aboutsummaryrefslogtreecommitdiff
path: root/src/or
diff options
context:
space:
mode:
Diffstat (limited to 'src/or')
-rw-r--r--src/or/buffers.c96
-rw-r--r--src/or/buffers.h3
-rw-r--r--src/or/channel.c47
-rw-r--r--src/or/channel.h1
-rw-r--r--src/or/channeltls.c3
-rw-r--r--src/or/circuitbuild.c564
-rw-r--r--src/or/circuitbuild.h9
-rw-r--r--src/or/circuitlist.c52
-rw-r--r--src/or/circuitlist.h3
-rw-r--r--src/or/circuitmux_ewma.c6
-rw-r--r--src/or/circuitstats.c4
-rw-r--r--src/or/circuituse.c134
-rw-r--r--src/or/circuituse.h3
-rw-r--r--src/or/command.c3
-rw-r--r--src/or/config.c60
-rw-r--r--src/or/config.h4
-rw-r--r--src/or/connection.c24
-rw-r--r--src/or/connection.h1
-rw-r--r--src/or/connection_edge.c462
-rw-r--r--src/or/connection_edge.h6
-rw-r--r--src/or/connection_or.c10
-rw-r--r--src/or/consdiffmgr.c1
-rw-r--r--src/or/control.c113
-rw-r--r--src/or/cpuworker.c4
-rw-r--r--src/or/dircollate.c2
-rw-r--r--src/or/directory.c278
-rw-r--r--src/or/directory.h43
-rw-r--r--src/or/dirserv.c18
-rw-r--r--src/or/dirvote.c9
-rw-r--r--src/or/dns.c20
-rw-r--r--src/or/entrynodes.c2
-rw-r--r--src/or/ext_orport.c2
-rw-r--r--src/or/hibernate.c5
-rw-r--r--src/or/hs_cache.c502
-rw-r--r--src/or/hs_cache.h60
-rw-r--r--src/or/hs_cell.c948
-rw-r--r--src/or/hs_cell.h122
-rw-r--r--src/or/hs_circuit.c1133
-rw-r--r--src/or/hs_circuit.h63
-rw-r--r--src/or/hs_circuitmap.c52
-rw-r--r--src/or/hs_circuitmap.h8
-rw-r--r--src/or/hs_client.c1253
-rw-r--r--src/or/hs_client.h50
-rw-r--r--src/or/hs_common.c1407
-rw-r--r--src/or/hs_common.h186
-rw-r--r--src/or/hs_config.c582
-rw-r--r--src/or/hs_config.h24
-rw-r--r--src/or/hs_descriptor.c420
-rw-r--r--src/or/hs_descriptor.h41
-rw-r--r--src/or/hs_ident.c110
-rw-r--r--src/or/hs_ident.h133
-rw-r--r--src/or/hs_intropoint.c27
-rw-r--r--src/or/hs_intropoint.h22
-rw-r--r--src/or/hs_ntor.c50
-rw-r--r--src/or/hs_ntor.h24
-rw-r--r--src/or/hs_service.c3330
-rw-r--r--src/or/hs_service.h359
-rw-r--r--src/or/include.am33
-rw-r--r--src/or/main.c79
-rw-r--r--src/or/microdesc.c2
-rw-r--r--src/or/networkstatus.c19
-rw-r--r--src/or/networkstatus.h1
-rw-r--r--src/or/nodelist.c171
-rw-r--r--src/or/nodelist.h6
-rw-r--r--src/or/ntmain.c6
-rw-r--r--src/or/onion.c2
-rw-r--r--src/or/onion_tap.c7
-rw-r--r--src/or/or.h53
-rw-r--r--src/or/parsecommon.c2
-rw-r--r--src/or/parsecommon.h3
-rw-r--r--src/or/policies.c2
-rw-r--r--src/or/protover.h5
-rw-r--r--src/or/relay.c42
-rw-r--r--src/or/rendcache.c4
-rw-r--r--src/or/rendclient.c424
-rw-r--r--src/or/rendclient.h4
-rw-r--r--src/or/rendcommon.c24
-rw-r--r--src/or/rendcommon.h11
-rw-r--r--src/or/rendmid.c1
-rw-r--r--src/or/rendservice.c547
-rw-r--r--src/or/rendservice.h28
-rw-r--r--src/or/rephist.c101
-rw-r--r--src/or/rephist.h3
-rw-r--r--src/or/router.c48
-rw-r--r--src/or/router.h2
-rw-r--r--src/or/routerkeys.c106
-rw-r--r--src/or/routerkeys.h1
-rw-r--r--src/or/routerlist.c14
-rw-r--r--src/or/routerparse.c25
-rw-r--r--src/or/scheduler.c16
-rw-r--r--src/or/shared_random.c46
-rw-r--r--src/or/shared_random.h3
-rw-r--r--src/or/shared_random_state.c46
-rw-r--r--src/or/shared_random_state.h5
-rw-r--r--src/or/statefile.c8
-rw-r--r--src/or/torcert.c2
96 files changed, 12819 insertions, 1981 deletions
diff --git a/src/or/buffers.c b/src/or/buffers.c
index 12a6c0239b..bd84103c37 100644
--- a/src/or/buffers.c
+++ b/src/or/buffers.c
@@ -80,7 +80,7 @@ static int parse_socks_client(const uint8_t *data, size_t datalen,
/* Chunk manipulation functions */
-#define CHUNK_HEADER_LEN STRUCT_OFFSET(chunk_t, mem[0])
+#define CHUNK_HEADER_LEN offsetof(chunk_t, mem[0])
/* We leave this many NUL bytes at the end of the buffer. */
#ifdef DISABLE_MEMORY_SENTINELS
@@ -1478,6 +1478,32 @@ socks_request_set_socks5_error(socks_request_t *req,
req->reply[3] = 0x01; // ATYP field.
}
+static const char SOCKS_PROXY_IS_NOT_AN_HTTP_PROXY_MSG[] =
+ "HTTP/1.0 501 Tor is not an HTTP Proxy\r\n"
+ "Content-Type: text/html; charset=iso-8859-1\r\n\r\n"
+ "<html>\n"
+ "<head>\n"
+ "<title>Tor is not an HTTP Proxy</title>\n"
+ "</head>\n"
+ "<body>\n"
+ "<h1>Tor is not an HTTP Proxy</h1>\n"
+ "<p>\n"
+ "It appears you have configured your web browser to use Tor as "
+ "an HTTP proxy.\n\n"
+ "This is not correct: Tor is a SOCKS proxy, not an HTTP proxy.\n"
+ "Please configure your client accordingly.\n"
+ "</p>\n"
+ "<p>\n"
+ "See <a href=\"https://www.torproject.org/documentation.html\">"
+ "https://www.torproject.org/documentation.html</a> for more "
+ "information.\n"
+ "<!-- Plus this comment, to make the body response more than 512 bytes, so "
+ " IE will be willing to display it. Comment comment comment comment "
+ " comment comment comment comment comment comment comment comment.-->\n"
+ "</p>\n"
+ "</body>\n"
+ "</html>\n";
+
/** Implementation helper to implement fetch_from_*_socks. Instead of looking
* at a buffer's contents, we look at the <b>datalen</b> bytes of data in
* <b>data</b>. Instead of removing data from the buffer, we set
@@ -1684,15 +1710,7 @@ parse_socks(const char *data, size_t datalen, socks_request_t *req,
req->port = ntohs(get_uint16(data+5+len));
*drain_out = 5+len+2;
- if (string_is_valid_ipv4_address(req->address) ||
- string_is_valid_ipv6_address(req->address)) {
- log_unsafe_socks_warning(5,req->address,req->port,safe_socks);
-
- if (safe_socks) {
- socks_request_set_socks5_error(req, SOCKS5_NOT_ALLOWED);
- return -1;
- }
- } else if (!string_is_valid_hostname(req->address)) {
+ if (!string_is_valid_hostname(req->address)) {
socks_request_set_socks5_error(req, SOCKS5_GENERAL_ERROR);
log_warn(LD_PROTOCOL,
@@ -1814,7 +1832,7 @@ parse_socks(const char *data, size_t datalen, socks_request_t *req,
log_debug(LD_APP,"socks4: Everything is here. Success.");
strlcpy(req->address, startaddr ? startaddr : tmpbuf,
sizeof(req->address));
- if (!tor_strisprint(req->address) || strchr(req->address,'\"')) {
+ if (!string_is_valid_hostname(req->address)) {
log_warn(LD_PROTOCOL,
"Your application (using socks4 to port %d) gave Tor "
"a malformed hostname: %s. Rejecting the connection.",
@@ -1834,32 +1852,8 @@ parse_socks(const char *data, size_t datalen, socks_request_t *req,
case 'H': /* head */
case 'P': /* put/post */
case 'C': /* connect */
- strlcpy((char*)req->reply,
-"HTTP/1.0 501 Tor is not an HTTP Proxy\r\n"
-"Content-Type: text/html; charset=iso-8859-1\r\n\r\n"
-"<html>\n"
-"<head>\n"
-"<title>Tor is not an HTTP Proxy</title>\n"
-"</head>\n"
-"<body>\n"
-"<h1>Tor is not an HTTP Proxy</h1>\n"
-"<p>\n"
-"It appears you have configured your web browser to use Tor as an HTTP proxy."
-"\n"
-"This is not correct: Tor is a SOCKS proxy, not an HTTP proxy.\n"
-"Please configure your client accordingly.\n"
-"</p>\n"
-"<p>\n"
-"See <a href=\"https://www.torproject.org/documentation.html\">"
- "https://www.torproject.org/documentation.html</a> for more "
- "information.\n"
-"<!-- Plus this comment, to make the body response more than 512 bytes, so "
-" IE will be willing to display it. Comment comment comment comment "
-" comment comment comment comment comment comment comment comment.-->\n"
-"</p>\n"
-"</body>\n"
-"</html>\n"
- , MAX_SOCKS_REPLY_LEN);
+ strlcpy((char*)req->reply, SOCKS_PROXY_IS_NOT_AN_HTTP_PROXY_MSG,
+ MAX_SOCKS_REPLY_LEN);
req->replylen = strlen((char*)req->reply)+1;
/* fall through */
default: /* version is not socks4 or socks5 */
@@ -2022,6 +2016,34 @@ parse_socks_client(const uint8_t *data, size_t datalen,
return -1;
}
+/** Return true if <b>cmd</b> looks like a HTTP (proxy) request. */
+int
+peek_buf_has_http_command(const buf_t *buf)
+{
+ if (peek_buf_startswith(buf, "CONNECT ") ||
+ peek_buf_startswith(buf, "DELETE ") ||
+ peek_buf_startswith(buf, "GET ") ||
+ peek_buf_startswith(buf, "POST ") ||
+ peek_buf_startswith(buf, "PUT " ))
+ return 1;
+ return 0;
+}
+
+/** Return 1 iff <b>buf</b> starts with <b>cmd</b>. <b>cmd</b> must be a null
+ * terminated string, of no more than PEEK_BUF_STARTSWITH_MAX bytes. */
+int
+peek_buf_startswith(const buf_t *buf, const char *cmd)
+{
+ char tmp[PEEK_BUF_STARTSWITH_MAX];
+ size_t clen = strlen(cmd);
+ if (BUG(clen > sizeof(tmp)))
+ return 0;
+ if (buf->datalen < clen)
+ return 0;
+ peek_from_buf(tmp, clen, buf);
+ return fast_memeq(tmp, cmd, clen);
+}
+
/** Return 1 iff buf looks more like it has an (obsolete) v0 controller
* command on it than any valid v1 controller command. */
int
diff --git a/src/or/buffers.h b/src/or/buffers.h
index 23b58a571a..d884084385 100644
--- a/src/or/buffers.h
+++ b/src/or/buffers.h
@@ -53,6 +53,9 @@ int fetch_from_buf_socks_client(buf_t *buf, int state, char **reason);
int fetch_from_buf_line(buf_t *buf, char *data_out, size_t *data_len);
int peek_buf_has_control0_command(buf_t *buf);
+#define PEEK_BUF_STARTSWITH_MAX 16
+int peek_buf_startswith(const buf_t *buf, const char *cmd);
+int peek_buf_has_http_command(const buf_t *buf);
int fetch_ext_or_command_from_buf(buf_t *buf, ext_or_cmd_t **out);
diff --git a/src/or/channel.c b/src/or/channel.c
index df6d7d3423..9f8a03683f 100644
--- a/src/or/channel.c
+++ b/src/or/channel.c
@@ -2086,8 +2086,8 @@ channel_write_var_cell(channel_t *chan, var_cell_t *var_cell)
* are appropriate to the state transition in question.
*/
-void
-channel_change_state(channel_t *chan, channel_state_t to_state)
+static void
+channel_change_state_(channel_t *chan, channel_state_t to_state)
{
channel_state_t from_state;
unsigned char was_active, is_active;
@@ -2206,18 +2206,8 @@ channel_change_state(channel_t *chan, channel_state_t to_state)
estimated_total_queue_size += chan->bytes_in_queue;
}
- /* Tell circuits if we opened and stuff */
- if (to_state == CHANNEL_STATE_OPEN) {
- channel_do_open_actions(chan);
- chan->has_been_open = 1;
-
- /* Check for queued cells to process */
- if (! TOR_SIMPLEQ_EMPTY(&chan->incoming_queue))
- channel_process_cells(chan);
- if (! TOR_SIMPLEQ_EMPTY(&chan->outgoing_queue))
- channel_flush_cells(chan);
- } else if (to_state == CHANNEL_STATE_CLOSED ||
- to_state == CHANNEL_STATE_ERROR) {
+ if (to_state == CHANNEL_STATE_CLOSED ||
+ to_state == CHANNEL_STATE_ERROR) {
/* Assert that all queues are empty */
tor_assert(TOR_SIMPLEQ_EMPTY(&chan->incoming_queue));
tor_assert(TOR_SIMPLEQ_EMPTY(&chan->outgoing_queue));
@@ -2225,6 +2215,35 @@ channel_change_state(channel_t *chan, channel_state_t to_state)
}
/**
+ * As channel_change_state_, but change the state to any state but open.
+ */
+void
+channel_change_state(channel_t *chan, channel_state_t to_state)
+{
+ tor_assert(to_state != CHANNEL_STATE_OPEN);
+ channel_change_state_(chan, to_state);
+}
+
+/**
+ * As channel_change_state, but change the state to open.
+ */
+void
+channel_change_state_open(channel_t *chan)
+{
+ channel_change_state_(chan, CHANNEL_STATE_OPEN);
+
+ /* Tell circuits if we opened and stuff */
+ channel_do_open_actions(chan);
+ chan->has_been_open = 1;
+
+ /* Check for queued cells to process */
+ if (! TOR_SIMPLEQ_EMPTY(&chan->incoming_queue))
+ channel_process_cells(chan);
+ if (! TOR_SIMPLEQ_EMPTY(&chan->outgoing_queue))
+ channel_flush_cells(chan);
+}
+
+/**
* Change channel listener state
*
* This internal and subclass use only function is used to change channel
diff --git a/src/or/channel.h b/src/or/channel.h
index ea280f2fd2..2d0ec39924 100644
--- a/src/or/channel.h
+++ b/src/or/channel.h
@@ -522,6 +522,7 @@ void channel_listener_free(channel_listener_t *chan_l);
/* State/metadata setters */
void channel_change_state(channel_t *chan, channel_state_t to_state);
+void channel_change_state_open(channel_t *chan);
void channel_clear_identity_digest(channel_t *chan);
void channel_clear_remote_end(channel_t *chan);
void channel_mark_local(channel_t *chan);
diff --git a/src/or/channeltls.c b/src/or/channeltls.c
index f44e4fc8ea..6547451181 100644
--- a/src/or/channeltls.c
+++ b/src/or/channeltls.c
@@ -993,7 +993,7 @@ channel_tls_handle_state_change_on_orconn(channel_tls_t *chan,
* We can go to CHANNEL_STATE_OPEN from CHANNEL_STATE_OPENING or
* CHANNEL_STATE_MAINT on this.
*/
- channel_change_state(base_chan, CHANNEL_STATE_OPEN);
+ channel_change_state_open(base_chan);
/* We might have just become writeable; check and tell the scheduler */
if (connection_or_num_cells_writeable(conn) > 0) {
scheduler_channel_wants_writes(base_chan);
@@ -1915,7 +1915,6 @@ certs_cell_typenum_to_cert_type(int typenum)
* of the connection, we then authenticate the server or mark the connection.
* If it's the server side, wait for an AUTHENTICATE cell.
*/
-
STATIC void
channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan)
{
diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c
index 16cef0e56b..65cd7bd5dc 100644
--- a/src/or/circuitbuild.c
+++ b/src/or/circuitbuild.c
@@ -46,6 +46,7 @@
#include "crypto.h"
#include "directory.h"
#include "entrynodes.h"
+#include "hs_ntor.h"
#include "main.h"
#include "microdesc.h"
#include "networkstatus.h"
@@ -74,6 +75,10 @@ static int onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit);
static crypt_path_t *onion_next_hop_in_cpath(crypt_path_t *cpath);
static int onion_extend_cpath(origin_circuit_t *circ);
static int onion_append_hop(crypt_path_t **head_ptr, extend_info_t *choice);
+static int circuit_send_first_onion_skin(origin_circuit_t *circ);
+static int circuit_build_no_more_hops(origin_circuit_t *circ);
+static int circuit_send_intermediate_onion_skin(origin_circuit_t *circ,
+ crypt_path_t *hop);
/** This function tries to get a channel to the specified endpoint,
* and then calls command_setup_channel() to give it the right
@@ -912,234 +917,275 @@ circuit_purpose_may_omit_guard(int purpose)
* If circ's first hop is closed, then we need to build a create
* cell and send it forward.
*
- * Otherwise, we need to build a relay extend cell and send it
- * forward.
+ * Otherwise, if circ's cpath still has any non-open hops, we need to
+ * build a relay extend cell and send it forward to the next non-open hop.
+ *
+ * If all hops on the cpath are open, we're done building the circuit
+ * and we should do housekeeping for the newly opened circuit.
*
* Return -reason if we want to tear down circ, else return 0.
*/
int
circuit_send_next_onion_skin(origin_circuit_t *circ)
{
- crypt_path_t *hop;
- const node_t *node;
-
tor_assert(circ);
if (circ->cpath->state == CPATH_STATE_CLOSED) {
- /* This is the first hop. */
- create_cell_t cc;
- int fast;
- int len;
- log_debug(LD_CIRC,"First skin; sending create cell.");
- memset(&cc, 0, sizeof(cc));
- if (circ->build_state->onehop_tunnel)
- control_event_bootstrap(BOOTSTRAP_STATUS_ONEHOP_CREATE, 0);
- else {
- control_event_bootstrap(BOOTSTRAP_STATUS_CIRCUIT_CREATE, 0);
-
- /* If this is not a one-hop tunnel, the channel is being used
- * for traffic that wants anonymity and protection from traffic
- * analysis (such as netflow record retention). That means we want
- * to pad it.
- */
- if (circ->base_.n_chan->channel_usage < CHANNEL_USED_FOR_FULL_CIRCS)
- circ->base_.n_chan->channel_usage = CHANNEL_USED_FOR_FULL_CIRCS;
- }
+ /* Case one: we're on the first hop. */
+ return circuit_send_first_onion_skin(circ);
+ }
- node = node_get_by_id(circ->base_.n_chan->identity_digest);
- fast = should_use_create_fast_for_circuit(circ);
- if (!fast) {
- /* We are an OR and we know the right onion key: we should
- * send a create cell.
- */
- circuit_pick_create_handshake(&cc.cell_type, &cc.handshake_type,
- circ->cpath->extend_info);
- } else {
- /* We are not an OR, and we're building the first hop of a circuit to a
- * new OR: we can be speedy and use CREATE_FAST to save an RSA operation
- * and a DH operation. */
- cc.cell_type = CELL_CREATE_FAST;
- cc.handshake_type = ONION_HANDSHAKE_TYPE_FAST;
- }
+ tor_assert(circ->cpath->state == CPATH_STATE_OPEN);
+ tor_assert(circ->base_.state == CIRCUIT_STATE_BUILDING);
+ crypt_path_t *hop = onion_next_hop_in_cpath(circ->cpath);
- len = onion_skin_create(cc.handshake_type,
- circ->cpath->extend_info,
- &circ->cpath->handshake_state,
- cc.onionskin);
- if (len < 0) {
- log_warn(LD_CIRC,"onion_skin_create (first hop) failed.");
- return - END_CIRC_REASON_INTERNAL;
- }
- cc.handshake_len = len;
+ if (hop) {
+ /* Case two: we're on a hop after the first. */
+ return circuit_send_intermediate_onion_skin(circ, hop);
+ }
+
+ /* Case three: the circuit is finished. Do housekeeping tasks on it. */
+ return circuit_build_no_more_hops(circ);
+}
- if (circuit_deliver_create_cell(TO_CIRCUIT(circ), &cc, 0) < 0)
- return - END_CIRC_REASON_RESOURCELIMIT;
+/**
+ * Called from circuit_send_next_onion_skin() when we find ourselves connected
+ * to the first hop in <b>circ</b>: Send a CREATE or CREATE2 or CREATE_FAST
+ * cell to that hop. Return 0 on success; -reason on failure (if the circuit
+ * should be torn down).
+ */
+static int
+circuit_send_first_onion_skin(origin_circuit_t *circ)
+{
+ int fast;
+ int len;
+ const node_t *node;
+ create_cell_t cc;
+ memset(&cc, 0, sizeof(cc));
+
+ log_debug(LD_CIRC,"First skin; sending create cell.");
- circ->cpath->state = CPATH_STATE_AWAITING_KEYS;
- circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING);
- log_info(LD_CIRC,"First hop: finished sending %s cell to '%s'",
- fast ? "CREATE_FAST" : "CREATE",
- node ? node_describe(node) : "<unnamed>");
+ if (circ->build_state->onehop_tunnel) {
+ control_event_bootstrap(BOOTSTRAP_STATUS_ONEHOP_CREATE, 0);
} else {
- extend_cell_t ec;
- int len;
- tor_assert(circ->cpath->state == CPATH_STATE_OPEN);
- tor_assert(circ->base_.state == CIRCUIT_STATE_BUILDING);
- log_debug(LD_CIRC,"starting to send subsequent skin.");
- hop = onion_next_hop_in_cpath(circ->cpath);
- memset(&ec, 0, sizeof(ec));
- if (!hop) {
- /* done building the circuit. whew. */
- guard_usable_t r;
- if (! circ->guard_state) {
- if (circuit_get_cpath_len(circ) != 1 &&
- ! circuit_purpose_may_omit_guard(circ->base_.purpose) &&
- get_options()->UseEntryGuards) {
- log_warn(LD_BUG, "%d-hop circuit %p with purpose %d has no "
- "guard state",
- circuit_get_cpath_len(circ), circ, circ->base_.purpose);
- }
- r = GUARD_USABLE_NOW;
- } else {
- r = entry_guard_succeeded(&circ->guard_state);
- }
- const int is_usable_for_streams = (r == GUARD_USABLE_NOW);
- if (r == GUARD_USABLE_NOW) {
- circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_OPEN);
- } else if (r == GUARD_MAYBE_USABLE_LATER) {
- // Wait till either a better guard succeeds, or till
- // all better guards fail.
- circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_GUARD_WAIT);
- } else {
- tor_assert_nonfatal(r == GUARD_USABLE_NEVER);
- return - END_CIRC_REASON_INTERNAL;
- }
+ control_event_bootstrap(BOOTSTRAP_STATUS_CIRCUIT_CREATE, 0);
- /* XXXX #21422 -- the rest of this branch needs careful thought!
- * Some of the things here need to happen when a circuit becomes
- * mechanically open; some need to happen when it is actually usable.
- * I think I got them right, but more checking would be wise. -NM
- */
+ /* If this is not a one-hop tunnel, the channel is being used
+ * for traffic that wants anonymity and protection from traffic
+ * analysis (such as netflow record retention). That means we want
+ * to pad it.
+ */
+ if (circ->base_.n_chan->channel_usage < CHANNEL_USED_FOR_FULL_CIRCS)
+ circ->base_.n_chan->channel_usage = CHANNEL_USED_FOR_FULL_CIRCS;
+ }
- if (circuit_timeout_want_to_count_circ(circ)) {
- struct timeval end;
- long timediff;
- tor_gettimeofday(&end);
- timediff = tv_mdiff(&circ->base_.timestamp_began, &end);
+ node = node_get_by_id(circ->base_.n_chan->identity_digest);
+ fast = should_use_create_fast_for_circuit(circ);
+ if (!fast) {
+ /* We know the right onion key: we should send a create cell. */
+ circuit_pick_create_handshake(&cc.cell_type, &cc.handshake_type,
+ circ->cpath->extend_info);
+ } else {
+ /* We don't know an onion key, so we need to fall back to CREATE_FAST. */
+ cc.cell_type = CELL_CREATE_FAST;
+ cc.handshake_type = ONION_HANDSHAKE_TYPE_FAST;
+ }
- /*
- * If the circuit build time is much greater than we would have cut
- * it off at, we probably had a suspend event along this codepath,
- * and we should discard the value.
- */
- if (timediff < 0 ||
- timediff > 2*get_circuit_build_close_time_ms()+1000) {
- log_notice(LD_CIRC, "Strange value for circuit build time: %ldmsec. "
- "Assuming clock jump. Purpose %d (%s)", timediff,
- circ->base_.purpose,
- circuit_purpose_to_string(circ->base_.purpose));
- } else if (!circuit_build_times_disabled(get_options())) {
- /* Only count circuit times if the network is live */
- if (circuit_build_times_network_check_live(
- get_circuit_build_times())) {
- circuit_build_times_add_time(get_circuit_build_times_mutable(),
- (build_time_t)timediff);
- circuit_build_times_set_timeout(get_circuit_build_times_mutable());
- }
-
- if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) {
- circuit_build_times_network_circ_success(
- get_circuit_build_times_mutable());
- }
- }
- }
- log_info(LD_CIRC,"circuit built!");
- circuit_reset_failure_count(0);
+ len = onion_skin_create(cc.handshake_type,
+ circ->cpath->extend_info,
+ &circ->cpath->handshake_state,
+ cc.onionskin);
+ if (len < 0) {
+ log_warn(LD_CIRC,"onion_skin_create (first hop) failed.");
+ return - END_CIRC_REASON_INTERNAL;
+ }
+ cc.handshake_len = len;
- if (circ->build_state->onehop_tunnel || circ->has_opened) {
- control_event_bootstrap(BOOTSTRAP_STATUS_REQUESTING_STATUS, 0);
- }
+ if (circuit_deliver_create_cell(TO_CIRCUIT(circ), &cc, 0) < 0)
+ return - END_CIRC_REASON_RESOURCELIMIT;
- pathbias_count_build_success(circ);
- circuit_rep_hist_note_result(circ);
- if (is_usable_for_streams)
- circuit_has_opened(circ); /* do other actions as necessary */
-
- if (!have_completed_a_circuit() && !circ->build_state->onehop_tunnel) {
- const or_options_t *options = get_options();
- note_that_we_completed_a_circuit();
- /* FFFF Log a count of known routers here */
- log_notice(LD_GENERAL,
- "Tor has successfully opened a circuit. "
- "Looks like client functionality is working.");
- if (control_event_bootstrap(BOOTSTRAP_STATUS_DONE, 0) == 0) {
- log_notice(LD_GENERAL,
- "Tor has successfully opened a circuit. "
- "Looks like client functionality is working.");
- }
- control_event_client_status(LOG_NOTICE, "CIRCUIT_ESTABLISHED");
- clear_broken_connection_map(1);
- if (server_mode(options) && !check_whether_orport_reachable(options)) {
- inform_testing_reachability();
- consider_testing_reachability(1, 1);
- }
+ circ->cpath->state = CPATH_STATE_AWAITING_KEYS;
+ circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING);
+ log_info(LD_CIRC,"First hop: finished sending %s cell to '%s'",
+ fast ? "CREATE_FAST" : "CREATE",
+ node ? node_describe(node) : "<unnamed>");
+ return 0;
+}
+
+/**
+ * Called from circuit_send_next_onion_skin() when we find that we have no
+ * more hops: mark the circuit as finished, and perform the necessary
+ * bookkeeping. Return 0 on success; -reason on failure (if the circuit
+ * should be torn down).
+ */
+static int
+circuit_build_no_more_hops(origin_circuit_t *circ)
+{
+ guard_usable_t r;
+ if (! circ->guard_state) {
+ if (circuit_get_cpath_len(circ) != 1 &&
+ ! circuit_purpose_may_omit_guard(circ->base_.purpose) &&
+ get_options()->UseEntryGuards) {
+ log_warn(LD_BUG, "%d-hop circuit %p with purpose %d has no "
+ "guard state",
+ circuit_get_cpath_len(circ), circ, circ->base_.purpose);
+ }
+ r = GUARD_USABLE_NOW;
+ } else {
+ r = entry_guard_succeeded(&circ->guard_state);
+ }
+ const int is_usable_for_streams = (r == GUARD_USABLE_NOW);
+ if (r == GUARD_USABLE_NOW) {
+ circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_OPEN);
+ } else if (r == GUARD_MAYBE_USABLE_LATER) {
+ // Wait till either a better guard succeeds, or till
+ // all better guards fail.
+ circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_GUARD_WAIT);
+ } else {
+ tor_assert_nonfatal(r == GUARD_USABLE_NEVER);
+ return - END_CIRC_REASON_INTERNAL;
+ }
+
+ /* XXXX #21422 -- the rest of this branch needs careful thought!
+ * Some of the things here need to happen when a circuit becomes
+ * mechanically open; some need to happen when it is actually usable.
+ * I think I got them right, but more checking would be wise. -NM
+ */
+
+ if (circuit_timeout_want_to_count_circ(circ)) {
+ struct timeval end;
+ long timediff;
+ tor_gettimeofday(&end);
+ timediff = tv_mdiff(&circ->base_.timestamp_began, &end);
+
+ /*
+ * If the circuit build time is much greater than we would have cut
+ * it off at, we probably had a suspend event along this codepath,
+ * and we should discard the value.
+ */
+ if (timediff < 0 ||
+ timediff > 2*get_circuit_build_close_time_ms()+1000) {
+ log_notice(LD_CIRC, "Strange value for circuit build time: %ldmsec. "
+ "Assuming clock jump. Purpose %d (%s)", timediff,
+ circ->base_.purpose,
+ circuit_purpose_to_string(circ->base_.purpose));
+ } else if (!circuit_build_times_disabled(get_options())) {
+ /* Only count circuit times if the network is live */
+ if (circuit_build_times_network_check_live(
+ get_circuit_build_times())) {
+ circuit_build_times_add_time(get_circuit_build_times_mutable(),
+ (build_time_t)timediff);
+ circuit_build_times_set_timeout(get_circuit_build_times_mutable());
}
- /* We're done with measurement circuits here. Just close them */
- if (circ->base_.purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) {
- circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED);
+ if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) {
+ circuit_build_times_network_circ_success(
+ get_circuit_build_times_mutable());
}
- return 0;
}
-
- if (tor_addr_family(&hop->extend_info->addr) != AF_INET) {
- log_warn(LD_BUG, "Trying to extend to a non-IPv4 address.");
- return - END_CIRC_REASON_INTERNAL;
+ }
+ log_info(LD_CIRC,"circuit built!");
+ circuit_reset_failure_count(0);
+
+ if (circ->build_state->onehop_tunnel || circ->has_opened) {
+ control_event_bootstrap(BOOTSTRAP_STATUS_REQUESTING_STATUS, 0);
+ }
+
+ pathbias_count_build_success(circ);
+ circuit_rep_hist_note_result(circ);
+ if (is_usable_for_streams)
+ circuit_has_opened(circ); /* do other actions as necessary */
+
+ if (!have_completed_a_circuit() && !circ->build_state->onehop_tunnel) {
+ const or_options_t *options = get_options();
+ note_that_we_completed_a_circuit();
+ /* FFFF Log a count of known routers here */
+ log_notice(LD_GENERAL,
+ "Tor has successfully opened a circuit. "
+ "Looks like client functionality is working.");
+ if (control_event_bootstrap(BOOTSTRAP_STATUS_DONE, 0) == 0) {
+ log_notice(LD_GENERAL,
+ "Tor has successfully opened a circuit. "
+ "Looks like client functionality is working.");
}
-
- circuit_pick_extend_handshake(&ec.cell_type,
- &ec.create_cell.cell_type,
- &ec.create_cell.handshake_type,
- hop->extend_info);
-
- tor_addr_copy(&ec.orport_ipv4.addr, &hop->extend_info->addr);
- ec.orport_ipv4.port = hop->extend_info->port;
- tor_addr_make_unspec(&ec.orport_ipv6.addr);
- memcpy(ec.node_id, hop->extend_info->identity_digest, DIGEST_LEN);
- /* Set the ED25519 identity too -- it will only get included
- * in the extend2 cell if we're configured to use it, though. */
- ed25519_pubkey_copy(&ec.ed_pubkey, &hop->extend_info->ed_identity);
-
- len = onion_skin_create(ec.create_cell.handshake_type,
- hop->extend_info,
- &hop->handshake_state,
- ec.create_cell.onionskin);
- if (len < 0) {
- log_warn(LD_CIRC,"onion_skin_create failed.");
- return - END_CIRC_REASON_INTERNAL;
+ control_event_client_status(LOG_NOTICE, "CIRCUIT_ESTABLISHED");
+ clear_broken_connection_map(1);
+ if (server_mode(options) && !check_whether_orport_reachable(options)) {
+ inform_testing_reachability();
+ consider_testing_reachability(1, 1);
}
- ec.create_cell.handshake_len = len;
+ }
- log_info(LD_CIRC,"Sending extend relay cell.");
- {
- uint8_t command = 0;
- uint16_t payload_len=0;
- uint8_t payload[RELAY_PAYLOAD_SIZE];
- if (extend_cell_format(&command, &payload_len, payload, &ec)<0) {
- log_warn(LD_CIRC,"Couldn't format extend cell");
- return -END_CIRC_REASON_INTERNAL;
- }
+ /* We're done with measurement circuits here. Just close them */
+ if (circ->base_.purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) {
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED);
+ }
+ return 0;
+}
+
+/**
+ * Called from circuit_send_next_onion_skin() when we find that we have a hop
+ * other than the first that we need to extend to: use <b>hop</b>'s
+ * information to extend the circuit another step. Return 0 on success;
+ * -reason on failure (if the circuit should be torn down).
+ */
+static int
+circuit_send_intermediate_onion_skin(origin_circuit_t *circ,
+ crypt_path_t *hop)
+{
+ int len;
+ extend_cell_t ec;
+ memset(&ec, 0, sizeof(ec));
+
+ log_debug(LD_CIRC,"starting to send subsequent skin.");
+
+ if (tor_addr_family(&hop->extend_info->addr) != AF_INET) {
+ log_warn(LD_BUG, "Trying to extend to a non-IPv4 address.");
+ return - END_CIRC_REASON_INTERNAL;
+ }
- /* send it to hop->prev, because it will transfer
- * it to a create cell and then send to hop */
- if (relay_send_command_from_edge(0, TO_CIRCUIT(circ),
- command,
- (char*)payload, payload_len,
- hop->prev) < 0)
- return 0; /* circuit is closed */
+ circuit_pick_extend_handshake(&ec.cell_type,
+ &ec.create_cell.cell_type,
+ &ec.create_cell.handshake_type,
+ hop->extend_info);
+
+ tor_addr_copy(&ec.orport_ipv4.addr, &hop->extend_info->addr);
+ ec.orport_ipv4.port = hop->extend_info->port;
+ tor_addr_make_unspec(&ec.orport_ipv6.addr);
+ memcpy(ec.node_id, hop->extend_info->identity_digest, DIGEST_LEN);
+ /* Set the ED25519 identity too -- it will only get included
+ * in the extend2 cell if we're configured to use it, though. */
+ ed25519_pubkey_copy(&ec.ed_pubkey, &hop->extend_info->ed_identity);
+
+ len = onion_skin_create(ec.create_cell.handshake_type,
+ hop->extend_info,
+ &hop->handshake_state,
+ ec.create_cell.onionskin);
+ if (len < 0) {
+ log_warn(LD_CIRC,"onion_skin_create failed.");
+ return - END_CIRC_REASON_INTERNAL;
+ }
+ ec.create_cell.handshake_len = len;
+
+ log_info(LD_CIRC,"Sending extend relay cell.");
+ {
+ uint8_t command = 0;
+ uint16_t payload_len=0;
+ uint8_t payload[RELAY_PAYLOAD_SIZE];
+ if (extend_cell_format(&command, &payload_len, payload, &ec)<0) {
+ log_warn(LD_CIRC,"Couldn't format extend cell");
+ return -END_CIRC_REASON_INTERNAL;
}
- hop->state = CPATH_STATE_AWAITING_KEYS;
+
+ /* send it to hop->prev, because that relay will transfer
+ * it to a create cell and then send to hop */
+ if (relay_send_command_from_edge(0, TO_CIRCUIT(circ),
+ command,
+ (char*)payload, payload_len,
+ hop->prev) < 0)
+ return 0; /* circuit is closed */
}
+ hop->state = CPATH_STATE_AWAITING_KEYS;
return 0;
}
@@ -1325,40 +1371,77 @@ circuit_extend(cell_t *cell, circuit_t *circ)
return 0;
}
-/** Initialize cpath-\>{f|b}_{crypto|digest} from the key material in
- * key_data. key_data must contain CPATH_KEY_MATERIAL bytes, which are
- * used as follows:
+/** Initialize cpath-\>{f|b}_{crypto|digest} from the key material in key_data.
+ *
+ * If <b>is_hs_v3</b> is set, this cpath will be used for next gen hidden
+ * service circuits and <b>key_data</b> must be at least
+ * HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN bytes in length.
+ *
+ * If <b>is_hs_v3</b> is not set, key_data must contain CPATH_KEY_MATERIAL_LEN
+ * bytes, which are used as follows:
* - 20 to initialize f_digest
* - 20 to initialize b_digest
* - 16 to key f_crypto
* - 16 to key b_crypto
*
* (If 'reverse' is true, then f_XX and b_XX are swapped.)
+ *
+ * Return 0 if init was successful, else -1 if it failed.
*/
int
-circuit_init_cpath_crypto(crypt_path_t *cpath, const char *key_data,
- int reverse)
+circuit_init_cpath_crypto(crypt_path_t *cpath,
+ const char *key_data, size_t key_data_len,
+ int reverse, int is_hs_v3)
{
crypto_digest_t *tmp_digest;
crypto_cipher_t *tmp_crypto;
+ size_t digest_len = 0;
+ size_t cipher_key_len = 0;
tor_assert(cpath);
tor_assert(key_data);
tor_assert(!(cpath->f_crypto || cpath->b_crypto ||
cpath->f_digest || cpath->b_digest));
- cpath->f_digest = crypto_digest_new();
- crypto_digest_add_bytes(cpath->f_digest, key_data, DIGEST_LEN);
- cpath->b_digest = crypto_digest_new();
- crypto_digest_add_bytes(cpath->b_digest, key_data+DIGEST_LEN, DIGEST_LEN);
+ /* Basic key size validation */
+ if (is_hs_v3 && BUG(key_data_len != HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN)) {
+ return -1;
+ } else if (!is_hs_v3 && BUG(key_data_len != CPATH_KEY_MATERIAL_LEN)) {
+ return -1;
+ }
+
+ /* If we are using this cpath for next gen onion services use SHA3-256,
+ otherwise use good ol' SHA1 */
+ if (is_hs_v3) {
+ digest_len = DIGEST256_LEN;
+ cipher_key_len = CIPHER256_KEY_LEN;
+ cpath->f_digest = crypto_digest256_new(DIGEST_SHA3_256);
+ cpath->b_digest = crypto_digest256_new(DIGEST_SHA3_256);
+ } else {
+ digest_len = DIGEST_LEN;
+ cipher_key_len = CIPHER_KEY_LEN;
+ cpath->f_digest = crypto_digest_new();
+ cpath->b_digest = crypto_digest_new();
+ }
+
+ tor_assert(digest_len != 0);
+ tor_assert(cipher_key_len != 0);
+ const int cipher_key_bits = (int) cipher_key_len * 8;
- if (!(cpath->f_crypto =
- crypto_cipher_new(key_data+(2*DIGEST_LEN)))) {
+ crypto_digest_add_bytes(cpath->f_digest, key_data, digest_len);
+ crypto_digest_add_bytes(cpath->b_digest, key_data+digest_len, digest_len);
+
+ cpath->f_crypto = crypto_cipher_new_with_bits(key_data+(2*digest_len),
+ cipher_key_bits);
+ if (!cpath->f_crypto) {
log_warn(LD_BUG,"Forward cipher initialization failed.");
return -1;
}
- if (!(cpath->b_crypto =
- crypto_cipher_new(key_data+(2*DIGEST_LEN)+CIPHER_KEY_LEN))) {
+
+ cpath->b_crypto = crypto_cipher_new_with_bits(
+ key_data+(2*digest_len)+cipher_key_len,
+ cipher_key_bits);
+ if (!cpath->b_crypto) {
log_warn(LD_BUG,"Backward cipher initialization failed.");
return -1;
}
@@ -1424,7 +1507,7 @@ circuit_finish_handshake(origin_circuit_t *circ,
onion_handshake_state_release(&hop->handshake_state);
- if (circuit_init_cpath_crypto(hop, keys, 0)<0) {
+ if (circuit_init_cpath_crypto(hop, keys, sizeof(keys), 0, 0)<0) {
return -END_CIRC_REASON_TORPROTOCOL;
}
@@ -1491,12 +1574,14 @@ circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer, int reason)
int
onionskin_answer(or_circuit_t *circ,
const created_cell_t *created_cell,
- const char *keys,
+ const char *keys, size_t keys_len,
const uint8_t *rend_circ_nonce)
{
cell_t cell;
crypt_path_t *tmp_cpath;
+ tor_assert(keys_len == CPATH_KEY_MATERIAL_LEN);
+
if (created_cell_format(&cell, created_cell) < 0) {
log_warn(LD_BUG,"couldn't format created cell (type=%d, len=%d)",
(int)created_cell->cell_type, (int)created_cell->handshake_len);
@@ -1512,7 +1597,7 @@ onionskin_answer(or_circuit_t *circ,
log_debug(LD_CIRC,"init digest forward 0x%.8x, backward 0x%.8x.",
(unsigned int)get_uint32(keys),
(unsigned int)get_uint32(keys+20));
- if (circuit_init_cpath_crypto(tmp_cpath, keys, 0)<0) {
+ if (circuit_init_cpath_crypto(tmp_cpath, keys, keys_len, 0, 0)<0) {
log_warn(LD_BUG,"Circuit initialization failed");
tor_free(tmp_cpath);
return -1;
@@ -1956,9 +2041,10 @@ choose_good_exit_server_general(int need_uptime, int need_capacity)
}
if (options->ExitNodes) {
log_warn(LD_CIRC,
- "No specified %sexit routers seem to be running: "
+ "No exits in ExitNodes%s seem to be running: "
"can't choose an exit.",
- options->ExcludeExitNodesUnion_ ? "non-excluded " : "");
+ options->ExcludeExitNodesUnion_ ?
+ ", except possibly those excluded by your configuration, " : "");
}
return NULL;
}
@@ -2311,6 +2397,30 @@ onion_append_to_cpath(crypt_path_t **head_ptr, crypt_path_t *new_hop)
}
}
+#ifdef TOR_UNIT_TESTS
+
+/** Unittest helper function: Count number of hops in cpath linked list. */
+unsigned int
+cpath_get_n_hops(crypt_path_t **head_ptr)
+{
+ unsigned int n_hops = 0;
+ crypt_path_t *tmp;
+
+ if (!*head_ptr) {
+ return 0;
+ }
+
+ tmp = *head_ptr;
+ if (tmp) {
+ n_hops++;
+ tmp = (*head_ptr)->next;
+ }
+
+ return n_hops;
+}
+
+#endif
+
/** A helper function used by onion_extend_cpath(). Use <b>purpose</b>
* and <b>state</b> and the cpath <b>head</b> (currently populated only
* to length <b>cur_len</b> to decide a suitable middle hop for a
@@ -2332,8 +2442,8 @@ choose_good_middle_server(uint8_t purpose,
tor_assert(CIRCUIT_PURPOSE_MIN_ <= purpose &&
purpose <= CIRCUIT_PURPOSE_MAX_);
- log_debug(LD_CIRC, "Contemplating intermediate hop %d: random choice.",
- cur_len);
+ log_debug(LD_CIRC, "Contemplating intermediate hop #%d: random choice.",
+ cur_len+1);
excluded = smartlist_new();
if ((r = build_state_get_exit_node(state))) {
nodelist_add_node_and_family(excluded, r);
@@ -2358,9 +2468,6 @@ choose_good_middle_server(uint8_t purpose,
* router (if we're an OR), and respect firewall settings; if we're
* configured to use entry guards, return one.
*
- * If <b>state</b> is NULL, we're choosing a router to serve as an entry
- * guard, not for any particular circuit.
- *
* Set *<b>guard_state_out</b> to information about the guard that
* we're selecting, which we'll use later to remember whether the
* guard worked or not.
@@ -2378,6 +2485,11 @@ choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state,
CRN_DIRECT_CONN);
const node_t *node;
+ /* Once we used this function to select a node to be a guard. We had
+ * 'state == NULL' be the signal for that. But we don't do that any more.
+ */
+ tor_assert_nonfatal(state);
+
if (state && options->UseEntryGuards &&
(purpose != CIRCUIT_PURPOSE_TESTING || options->BridgeRelay)) {
/* This request is for an entry server to use for a regular circuit,
@@ -2467,12 +2579,12 @@ onion_extend_cpath(origin_circuit_t *circ)
}
if (!info) {
- log_warn(LD_CIRC,"Failed to find node for hop %d of our path. Discarding "
- "this circuit.", cur_len);
+ log_warn(LD_CIRC,"Failed to find node for hop #%d of our path. Discarding "
+ "this circuit.", cur_len+1);
return -1;
}
- log_debug(LD_CIRC,"Chose router %s for hop %d (exit is %s)",
+ log_debug(LD_CIRC,"Chose router %s for hop #%d (exit is %s)",
extend_info_describe(info),
cur_len+1, build_state_get_exit_nickname(state));
@@ -2580,7 +2692,7 @@ extend_info_from_node(const node_t *node, int for_direct_connect)
ed_pubkey = node_get_ed25519_id(node);
} else if (node_get_ed25519_id(node)) {
log_info(LD_CIRC, "Not including the ed25519 ID for %s, since it won't "
- " be able to authenticate it.",
+ "be able to authenticate it.",
node_describe(node));
}
diff --git a/src/or/circuitbuild.h b/src/or/circuitbuild.h
index 45d9b2fb75..62a6367ed2 100644
--- a/src/or/circuitbuild.h
+++ b/src/or/circuitbuild.h
@@ -31,8 +31,9 @@ int circuit_timeout_want_to_count_circ(origin_circuit_t *circ);
int circuit_send_next_onion_skin(origin_circuit_t *circ);
void circuit_note_clock_jumped(int seconds_elapsed);
int circuit_extend(cell_t *cell, circuit_t *circ);
-int circuit_init_cpath_crypto(crypt_path_t *cpath, const char *key_data,
- int reverse);
+int circuit_init_cpath_crypto(crypt_path_t *cpath,
+ const char *key_data, size_t key_data_len,
+ int reverse, int is_hs_v3);
struct created_cell_t;
int circuit_finish_handshake(origin_circuit_t *circ,
const struct created_cell_t *created_cell);
@@ -40,7 +41,7 @@ int circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer,
int reason);
int onionskin_answer(or_circuit_t *circ,
const struct created_cell_t *created_cell,
- const char *keys,
+ const char *keys, size_t keys_len,
const uint8_t *rend_circ_nonce);
MOCK_DECL(int, circuit_all_predicted_ports_handled, (time_t now,
int *need_uptime,
@@ -83,6 +84,8 @@ MOCK_DECL(STATIC int, count_acceptable_nodes, (smartlist_t *nodes));
#if defined(ENABLE_TOR2WEB_MODE) || defined(TOR_UNIT_TESTS)
STATIC const node_t *pick_tor2web_rendezvous_node(router_crn_flags_t flags,
const or_options_t *options);
+unsigned int cpath_get_n_hops(crypt_path_t **head_ptr);
+
#endif
#endif
diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c
index 86b0aa097a..2f3fe327e6 100644
--- a/src/or/circuitlist.c
+++ b/src/or/circuitlist.c
@@ -65,8 +65,10 @@
#include "control.h"
#include "entrynodes.h"
#include "main.h"
+#include "hs_circuit.h"
#include "hs_circuitmap.h"
#include "hs_common.h"
+#include "hs_ident.h"
#include "networkstatus.h"
#include "nodelist.h"
#include "onion.h"
@@ -957,6 +959,7 @@ circuit_free(circuit_t *circ)
crypto_pk_free(ocirc->intro_key);
rend_data_free(ocirc->rend_data);
+ hs_ident_circuit_free(ocirc->hs_ident);
tor_free(ocirc->dest_address);
if (ocirc->socks_username) {
@@ -1530,6 +1533,41 @@ circuit_get_next_service_intro_circ(origin_circuit_t *start)
return NULL;
}
+/** Return the first service rendezvous circuit originating from the global
+ * circuit list after <b>start</b> or at the start of the list if <b>start</b>
+ * is NULL. Return NULL if no circuit is found.
+ *
+ * A service rendezvous point circuit has a purpose of either
+ * CIRCUIT_PURPOSE_S_CONNECT_REND or CIRCUIT_PURPOSE_S_REND_JOINED. This does
+ * not return a circuit marked for close and its state must be open. */
+origin_circuit_t *
+circuit_get_next_service_rp_circ(origin_circuit_t *start)
+{
+ int idx = 0;
+ smartlist_t *lst = circuit_get_global_list();
+
+ if (start) {
+ idx = TO_CIRCUIT(start)->global_circuitlist_idx + 1;
+ }
+
+ for ( ; idx < smartlist_len(lst); ++idx) {
+ circuit_t *circ = smartlist_get(lst, idx);
+
+ /* Ignore a marked for close circuit or purpose not matching a service
+ * intro point or if the state is not open. */
+ if (circ->marked_for_close || circ->state != CIRCUIT_STATE_OPEN ||
+ (circ->purpose != CIRCUIT_PURPOSE_S_CONNECT_REND &&
+ circ->purpose != CIRCUIT_PURPOSE_S_REND_JOINED)) {
+ continue;
+ }
+ /* The purposes we are looking for are only for origin circuits so the
+ * following is valid. */
+ return TO_ORIGIN_CIRCUIT(circ);
+ }
+ /* Not found. */
+ return NULL;
+}
+
/** Return the first circuit originating here in global_circuitlist after
* <b>start</b> whose purpose is <b>purpose</b>, and where <b>digest</b> (if
* set) matches the private key digest of the rend data associated with the
@@ -1911,13 +1949,20 @@ circuit_about_to_free(circuit_t *circ)
orig_reason);
}
+ /* Notify the HS subsystem for any intro point circuit closing so it can be
+ * dealt with cleanly. */
+ if (circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO ||
+ circ->purpose == CIRCUIT_PURPOSE_S_INTRO) {
+ hs_service_intro_circ_has_closed(TO_ORIGIN_CIRCUIT(circ));
+ }
+
if (circ->purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) {
origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
int timed_out = (reason == END_CIRC_REASON_TIMEOUT);
tor_assert(circ->state == CIRCUIT_STATE_OPEN);
tor_assert(ocirc->build_state->chosen_exit);
- tor_assert(ocirc->rend_data);
- if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT) {
+ if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT &&
+ ocirc->rend_data) {
/* treat this like getting a nack from it */
log_info(LD_REND, "Failed intro circ %s to %s (awaiting ack). %s",
safe_str_client(rend_data_get_address(ocirc->rend_data)),
@@ -1933,7 +1978,8 @@ circuit_about_to_free(circuit_t *circ)
reason != END_CIRC_REASON_TIMEOUT) {
origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
if (ocirc->build_state->chosen_exit && ocirc->rend_data) {
- if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT) {
+ if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT &&
+ ocirc->rend_data) {
log_info(LD_REND, "Failed intro circ %s to %s "
"(building circuit to intro point). "
"Marking intro point as possibly unreachable.",
diff --git a/src/or/circuitlist.h b/src/or/circuitlist.h
index d647062f46..b882b2c6a8 100644
--- a/src/or/circuitlist.h
+++ b/src/or/circuitlist.h
@@ -13,6 +13,7 @@
#define TOR_CIRCUITLIST_H
#include "testsupport.h"
+#include "hs_ident.h"
MOCK_DECL(smartlist_t *, circuit_get_global_list, (void));
smartlist_t *circuit_get_global_origin_circuit_list(void);
@@ -48,6 +49,8 @@ origin_circuit_t *circuit_get_ready_rend_circ_by_rend_data(
origin_circuit_t *circuit_get_next_by_pk_and_purpose(origin_circuit_t *start,
const uint8_t *digest, uint8_t purpose);
origin_circuit_t *circuit_get_next_service_intro_circ(origin_circuit_t *start);
+origin_circuit_t *circuit_get_next_service_rp_circ(origin_circuit_t *start);
+origin_circuit_t *circuit_get_next_service_hsdir_circ(origin_circuit_t *start);
origin_circuit_t *circuit_find_to_cannibalize(uint8_t purpose,
extend_info_t *info, int flags);
void circuit_mark_all_unused_circs(void);
diff --git a/src/or/circuitmux_ewma.c b/src/or/circuitmux_ewma.c
index c2440b13f0..fde2d22a89 100644
--- a/src/or/circuitmux_ewma.c
+++ b/src/or/circuitmux_ewma.c
@@ -731,7 +731,7 @@ add_cell_ewma(ewma_policy_data_t *pol, cell_ewma_t *ewma)
smartlist_pqueue_add(pol->active_circuit_pqueue,
compare_cell_ewma_counts,
- STRUCT_OFFSET(cell_ewma_t, heap_index),
+ offsetof(cell_ewma_t, heap_index),
ewma);
}
@@ -746,7 +746,7 @@ remove_cell_ewma(ewma_policy_data_t *pol, cell_ewma_t *ewma)
smartlist_pqueue_remove(pol->active_circuit_pqueue,
compare_cell_ewma_counts,
- STRUCT_OFFSET(cell_ewma_t, heap_index),
+ offsetof(cell_ewma_t, heap_index),
ewma);
}
@@ -760,6 +760,6 @@ pop_first_cell_ewma(ewma_policy_data_t *pol)
return smartlist_pqueue_pop(pol->active_circuit_pqueue,
compare_cell_ewma_counts,
- STRUCT_OFFSET(cell_ewma_t, heap_index));
+ offsetof(cell_ewma_t, heap_index));
}
diff --git a/src/or/circuitstats.c b/src/or/circuitstats.c
index 51d580a1a4..963892c9d1 100644
--- a/src/or/circuitstats.c
+++ b/src/or/circuitstats.c
@@ -939,7 +939,7 @@ circuit_build_times_parse_state(circuit_build_times_t *cbt,
uint32_t count, k;
build_time_t ms;
int ok;
- ms = (build_time_t)tor_parse_ulong(ms_str, 0, 0,
+ ms = (build_time_t)tor_parse_ulong(ms_str, 10, 0,
CBT_BUILD_TIME_MAX, &ok, NULL);
if (!ok) {
log_warn(LD_GENERAL, "Unable to parse circuit build times: "
@@ -949,7 +949,7 @@ circuit_build_times_parse_state(circuit_build_times_t *cbt,
smartlist_free(args);
break;
}
- count = (uint32_t)tor_parse_ulong(count_str, 0, 0,
+ count = (uint32_t)tor_parse_ulong(count_str, 10, 0,
UINT32_MAX, &ok, NULL);
if (!ok) {
log_warn(LD_GENERAL, "Unable to parse circuit build times: "
diff --git a/src/or/circuituse.c b/src/or/circuituse.c
index 9f9d3abf7c..570b05e572 100644
--- a/src/or/circuituse.c
+++ b/src/or/circuituse.c
@@ -42,6 +42,9 @@
#include "control.h"
#include "entrynodes.h"
#include "hs_common.h"
+#include "hs_client.h"
+#include "hs_circuit.h"
+#include "hs_ident.h"
#include "nodelist.h"
#include "networkstatus.h"
#include "policies.h"
@@ -55,6 +58,36 @@
static void circuit_expire_old_circuits_clientside(void);
static void circuit_increment_failure_count(void);
+/** Check whether the hidden service destination of the stream at
+ * <b>edge_conn</b> is the same as the destination of the circuit at
+ * <b>origin_circ</b>. */
+static int
+circuit_matches_with_rend_stream(const edge_connection_t *edge_conn,
+ const origin_circuit_t *origin_circ)
+{
+ /* Check if this is a v2 rendezvous circ/stream */
+ if ((edge_conn->rend_data && !origin_circ->rend_data) ||
+ (!edge_conn->rend_data && origin_circ->rend_data) ||
+ (edge_conn->rend_data && origin_circ->rend_data &&
+ rend_cmp_service_ids(rend_data_get_address(edge_conn->rend_data),
+ rend_data_get_address(origin_circ->rend_data)))) {
+ /* this circ is not for this conn */
+ return 0;
+ }
+
+ /* Check if this is a v3 rendezvous circ/stream */
+ if ((edge_conn->hs_ident && !origin_circ->hs_ident) ||
+ (!edge_conn->hs_ident && origin_circ->hs_ident) ||
+ (edge_conn->hs_ident && origin_circ->hs_ident &&
+ !ed25519_pubkey_eq(&edge_conn->hs_ident->identity_pk,
+ &origin_circ->hs_ident->identity_pk))) {
+ /* this circ is not for this conn */
+ return 0;
+ }
+
+ return 1;
+}
+
/** Return 1 if <b>circ</b> could be returned by circuit_get_best().
* Else return 0.
*/
@@ -169,14 +202,9 @@ circuit_is_acceptable(const origin_circuit_t *origin_circ,
/* can't exit from this router */
return 0;
}
- } else { /* not general */
+ } else { /* not general: this might be a rend circuit */
const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn);
- if ((edge_conn->rend_data && !origin_circ->rend_data) ||
- (!edge_conn->rend_data && origin_circ->rend_data) ||
- (edge_conn->rend_data && origin_circ->rend_data &&
- rend_cmp_service_ids(rend_data_get_address(edge_conn->rend_data),
- rend_data_get_address(origin_circ->rend_data)))) {
- /* this circ is not for this conn */
+ if (!circuit_matches_with_rend_stream(edge_conn, origin_circ)) {
return 0;
}
}
@@ -309,7 +337,8 @@ circuit_get_best(const entry_connection_t *conn,
/* Log an info message if we're going to launch a new intro circ in
* parallel */
if (purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT &&
- !must_be_open && origin_circ->hs_circ_has_timed_out) {
+ !must_be_open && origin_circ->hs_circ_has_timed_out &&
+ !circ->marked_for_close) {
intro_going_on_but_too_old = 1;
continue;
}
@@ -622,6 +651,7 @@ circuit_expire_building(void)
* because that's set when they switch purposes
*/
if (TO_ORIGIN_CIRCUIT(victim)->rend_data ||
+ TO_ORIGIN_CIRCUIT(victim)->hs_ident ||
victim->timestamp_dirty > cutoff.tv_sec)
continue;
break;
@@ -755,7 +785,7 @@ circuit_expire_building(void)
victim->state, circuit_state_to_string(victim->state),
victim->purpose);
TO_ORIGIN_CIRCUIT(victim)->hs_circ_has_timed_out = 1;
- rend_service_relaunch_rendezvous(TO_ORIGIN_CIRCUIT(victim));
+ hs_circ_retry_service_rendezvous_point(TO_ORIGIN_CIRCUIT(victim));
continue;
}
@@ -1086,11 +1116,32 @@ needs_exit_circuits(time_t now, int *needs_uptime, int *needs_capacity)
/* Return true if we need any more hidden service server circuits.
* HS servers only need an internal circuit. */
STATIC int
-needs_hs_server_circuits(int num_uptime_internal)
+needs_hs_server_circuits(time_t now, int num_uptime_internal)
{
- return (num_rend_services() &&
- num_uptime_internal < SUFFICIENT_UPTIME_INTERNAL_HS_SERVERS &&
- router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN);
+ if (!rend_num_services() && !hs_service_get_num_services()) {
+ /* No services, we don't need anything. */
+ goto no_need;
+ }
+
+ if (num_uptime_internal >= SUFFICIENT_UPTIME_INTERNAL_HS_SERVERS) {
+ /* We have sufficient amount of internal circuit. */
+ goto no_need;
+ }
+
+ if (router_have_consensus_path() == CONSENSUS_PATH_UNKNOWN) {
+ /* Consensus hasn't been checked or might be invalid so requesting
+ * internal circuits is not wise. */
+ goto no_need;
+ }
+
+ /* At this point, we need a certain amount of circuits and we will most
+ * likely use them for rendezvous so we note down the use of internal
+ * circuit for our prediction for circuit needing uptime and capacity. */
+ rep_hist_note_used_internal(now, 1, 1);
+
+ return 1;
+ no_need:
+ return 0;
}
/* We need at least this many internal circuits for hidden service clients */
@@ -1189,7 +1240,7 @@ circuit_predict_and_launch_new(void)
return;
}
- if (needs_hs_server_circuits(num_uptime_internal)) {
+ if (needs_hs_server_circuits(now, num_uptime_internal)) {
flags = (CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_NEED_UPTIME |
CIRCLAUNCH_IS_INTERNAL);
@@ -1253,11 +1304,6 @@ circuit_build_needed_circs(time_t now)
if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN)
connection_ap_rescan_and_attach_pending();
- /* make sure any hidden services have enough intro points
- * HS intro point streams only require an internal circuit */
- if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN)
- rend_consider_services_intro_points();
-
circuit_expire_old_circs_as_needed(now);
if (!options->DisablePredictedCircuits)
@@ -1339,8 +1385,7 @@ circuit_detach_stream(circuit_t *circ, edge_connection_t *conn)
* number of streams on the circuit associated with the rend service.
*/
if (circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED) {
- tor_assert(origin_circ->rend_data);
- origin_circ->rend_data->nr_streams--;
+ hs_dec_rdv_stream_counter(origin_circ);
}
return;
}
@@ -1593,7 +1638,7 @@ circuit_has_opened(origin_circuit_t *circ)
switch (TO_CIRCUIT(circ)->purpose) {
case CIRCUIT_PURPOSE_C_ESTABLISH_REND:
- rend_client_rendcirc_has_opened(circ);
+ hs_client_circuit_has_opened(circ);
/* Start building an intro circ if we don't have one yet. */
connection_ap_attach_pending(1);
/* This isn't a call to circuit_try_attaching_streams because a
@@ -1605,7 +1650,7 @@ circuit_has_opened(origin_circuit_t *circ)
* state. */
break;
case CIRCUIT_PURPOSE_C_INTRODUCING:
- rend_client_introcirc_has_opened(circ);
+ hs_client_circuit_has_opened(circ);
break;
case CIRCUIT_PURPOSE_C_GENERAL:
/* Tell any AP connections that have been waiting for a new
@@ -1614,11 +1659,11 @@ circuit_has_opened(origin_circuit_t *circ)
break;
case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO:
/* at the service, waiting for introductions */
- rend_service_intro_has_opened(circ);
+ hs_service_circuit_has_opened(circ);
break;
case CIRCUIT_PURPOSE_S_CONNECT_REND:
/* at the service, connecting to rend point */
- rend_service_rendezvous_has_opened(circ);
+ hs_service_circuit_has_opened(circ);
break;
case CIRCUIT_PURPOSE_TESTING:
circuit_testing_opened(circ);
@@ -1768,7 +1813,7 @@ circuit_build_failed(origin_circuit_t *circ)
"(%s hop failed).",
escaped(build_state_get_exit_nickname(circ->build_state)),
failed_at_last_hop?"last":"non-last");
- rend_service_relaunch_rendezvous(circ);
+ hs_circ_retry_service_rendezvous_point(circ);
break;
/* default:
* This won't happen in normal operation, but might happen if the
@@ -2131,22 +2176,25 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
/* If this is a hidden service trying to start an introduction point,
* handle that case. */
if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) {
+ const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn);
/* need to pick an intro point */
- rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data;
- tor_assert(rend_data);
- extend_info = rend_client_get_random_intro(rend_data);
+ extend_info = hs_client_get_random_intro_from_edge(edge_conn);
if (!extend_info) {
- log_info(LD_REND,
- "No intro points for '%s': re-fetching service descriptor.",
- safe_str_client(rend_data_get_address(rend_data)));
- rend_client_refetch_v2_renddesc(rend_data);
+ log_info(LD_REND, "No intro points: re-fetching service descriptor.");
+ if (edge_conn->rend_data) {
+ rend_client_refetch_v2_renddesc(edge_conn->rend_data);
+ } else {
+ hs_client_refetch_hsdesc(&edge_conn->hs_ident->identity_pk);
+ }
connection_ap_mark_as_non_pending_circuit(conn);
ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_RENDDESC_WAIT;
return 0;
}
log_info(LD_REND,"Chose %s as intro point for '%s'.",
extend_info_describe(extend_info),
- safe_str_client(rend_data_get_address(rend_data)));
+ (edge_conn->rend_data) ?
+ safe_str_client(rend_data_get_address(edge_conn->rend_data)) :
+ "service");
}
/* If we have specified a particular exit node for our
@@ -2265,8 +2313,15 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
/* help predict this next time */
rep_hist_note_used_internal(time(NULL), need_uptime, 1);
if (circ) {
- /* write the service_id into circ */
- circ->rend_data = rend_data_dup(ENTRY_TO_EDGE_CONN(conn)->rend_data);
+ const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn);
+ if (edge_conn->rend_data) {
+ /* write the service_id into circ */
+ circ->rend_data = rend_data_dup(edge_conn->rend_data);
+ } else if (edge_conn->hs_ident) {
+ circ->hs_ident =
+ hs_ident_circuit_new(&edge_conn->hs_ident->identity_pk,
+ HS_IDENT_CIRCUIT_INTRO);
+ }
if (circ->base_.purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND &&
circ->base_.state == CIRCUIT_STATE_OPEN)
circuit_has_opened(circ);
@@ -2348,8 +2403,7 @@ link_apconn_to_circ(entry_connection_t *apconn, origin_circuit_t *circ,
/* We are attaching a stream to a rendezvous circuit. That means
* that an attempt to connect to a hidden service just
* succeeded. Tell rendclient.c. */
- rend_client_note_connection_attempt_ended(
- ENTRY_TO_EDGE_CONN(apconn)->rend_data);
+ hs_client_note_connection_attempt_succeeded(ENTRY_TO_EDGE_CONN(apconn));
}
if (cpath) { /* we were given one; use it */
@@ -2695,12 +2749,14 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn)
tor_assert(introcirc->base_.purpose == CIRCUIT_PURPOSE_C_INTRODUCING);
if (introcirc->base_.state == CIRCUIT_STATE_OPEN) {
+ int ret;
log_info(LD_REND,"found open intro circ %u (rend %u); sending "
"introduction. (stream %d sec old)",
(unsigned)introcirc->base_.n_circ_id,
(unsigned)rendcirc->base_.n_circ_id,
conn_age);
- switch (rend_client_send_introduction(introcirc, rendcirc)) {
+ ret = hs_client_send_introduce1(introcirc, rendcirc);
+ switch (ret) {
case 0: /* success */
rendcirc->base_.timestamp_dirty = time(NULL);
introcirc->base_.timestamp_dirty = time(NULL);
diff --git a/src/or/circuituse.h b/src/or/circuituse.h
index ad4c214a3b..e66679586d 100644
--- a/src/or/circuituse.h
+++ b/src/or/circuituse.h
@@ -68,7 +68,8 @@ STATIC int circuit_is_available_for_use(const circuit_t *circ);
STATIC int needs_exit_circuits(time_t now,
int *port_needs_uptime,
int *port_needs_capacity);
-STATIC int needs_hs_server_circuits(int num_uptime_internal);
+STATIC int needs_hs_server_circuits(time_t now,
+ int num_uptime_internal);
STATIC int needs_hs_client_circuits(time_t now,
int *needs_uptime,
diff --git a/src/or/command.c b/src/or/command.c
index c667cbbe52..2c82984901 100644
--- a/src/or/command.c
+++ b/src/or/command.c
@@ -381,7 +381,8 @@ command_process_create_cell(cell_t *cell, channel_t *chan)
created_cell.handshake_len = len;
if (onionskin_answer(circ, &created_cell,
- (const char *)keys, rend_circ_nonce)<0) {
+ (const char *)keys, sizeof(keys),
+ rend_circ_nonce)<0) {
log_warn(LD_OR,"Failed to reply to CREATE_FAST cell. Closing.");
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
return;
diff --git a/src/or/config.c b/src/or/config.c
index 67bf3b9671..30853724e4 100644
--- a/src/or/config.c
+++ b/src/or/config.c
@@ -91,6 +91,7 @@
#include "relay.h"
#include "rendclient.h"
#include "rendservice.h"
+#include "hs_config.h"
#include "rephist.h"
#include "router.h"
#include "sandbox.h"
@@ -176,7 +177,7 @@ static config_abbrev_t option_abbrevs_[] = {
* or_options_t.<b>member</b>"
*/
#define VAR(name,conftype,member,initvalue) \
- { name, CONFIG_TYPE_ ## conftype, STRUCT_OFFSET(or_options_t, member), \
+ { name, CONFIG_TYPE_ ## conftype, offsetof(or_options_t, member), \
initvalue }
/** As VAR, but the option name and member name are the same. */
#define V(member,conftype,initvalue) \
@@ -408,6 +409,7 @@ static config_var_t option_vars_[] = {
OBSOLETE("PredictedPortsRelevanceTime"),
OBSOLETE("WarnUnsafeSocks"),
VAR("NodeFamily", LINELIST, NodeFamilies, NULL),
+ V(NoExec, BOOL, "0"),
V(NumCPUs, UINT, "0"),
V(NumDirectoryGuards, UINT, "0"),
V(NumEntryGuards, UINT, "0"),
@@ -674,6 +676,13 @@ static const config_deprecation_t option_deprecation_notes_[] = {
"easier to fingerprint, and may open you to esoteric attacks." },
/* End of options deprecated since 0.2.9.2-alpha. */
+ /* Deprecated since 0.3.2.0-alpha. */
+ { "HTTPProxy", "It only applies to direct unencrypted HTTP connections "
+ "to your directory server, which your Tor probably wasn't using." },
+ { "HTTPProxyAuthenticator", "HTTPProxy is deprecated in favor of HTTPSProxy "
+ "which should be used with HTTPSProxyAuthenticator." },
+ /* End of options deprecated since 0.3.2.0-alpha. */
+
{ NULL, NULL }
};
@@ -725,7 +734,7 @@ static uint64_t compute_real_max_mem_in_queues(const uint64_t val,
STATIC config_format_t options_format = {
sizeof(or_options_t),
OR_OPTIONS_MAGIC,
- STRUCT_OFFSET(or_options_t, magic_),
+ offsetof(or_options_t, magic_),
option_abbrevs_,
option_deprecation_notes_,
option_vars_,
@@ -1587,6 +1596,10 @@ options_act(const or_options_t *old_options)
const int transition_affects_guards =
old_options && options_transition_affects_guards(old_options, options);
+ if (options->NoExec || options->Sandbox) {
+ tor_disable_spawning_background_processes();
+ }
+
/* disable ptrace and later, other basic debugging techniques */
{
/* Remember if we already disabled debugger attachment */
@@ -1675,7 +1688,7 @@ options_act(const or_options_t *old_options)
sweep_bridge_list();
}
- if (running_tor && rend_config_services(options, 0)<0) {
+ if (running_tor && hs_config_service_all(options, 0)<0) {
log_warn(LD_BUG,
"Previously validated hidden services line could not be added!");
return -1;
@@ -1792,7 +1805,7 @@ options_act(const or_options_t *old_options)
monitor_owning_controller_process(options->OwningControllerProcess);
/* reload keys as needed for rendezvous services. */
- if (rend_service_load_all_keys(NULL)<0) {
+ if (hs_service_load_all_keys() < 0) {
log_warn(LD_GENERAL,"Error loading rendezvous service keys");
return -1;
}
@@ -2129,6 +2142,7 @@ static const struct {
{ "--dump-config", ARGUMENT_OPTIONAL },
{ "--list-fingerprint", TAKES_NO_ARGUMENT },
{ "--keygen", TAKES_NO_ARGUMENT },
+ { "--key-expiration", ARGUMENT_OPTIONAL },
{ "--newpass", TAKES_NO_ARGUMENT },
{ "--no-passphrase", TAKES_NO_ARGUMENT },
{ "--passphrase-fd", ARGUMENT_NECESSARY },
@@ -2798,10 +2812,6 @@ compute_publishserverdescriptor(or_options_t *options)
* will generate too many circuits and potentially overload the network. */
#define MIN_CIRCUIT_STREAM_TIMEOUT 10
-/** Lowest allowable value for HeartbeatPeriod; if this is too low, we might
- * expose more information than we're comfortable with. */
-#define MIN_HEARTBEAT_PERIOD (30*60)
-
/** Lowest recommended value for CircuitBuildTimeout; if it is set too low
* and LearnCircuitBuildTimeout is off, the failure rate for circuit
* construction may be very high. In that case, if it is set below this
@@ -3155,7 +3165,7 @@ options_validate(or_options_t *old_options, or_options_t *options,
"UseEntryGuards. Disabling.");
options->UseEntryGuards = 0;
}
- if (!options->DownloadExtraInfo && authdir_mode_any_main(options)) {
+ if (!options->DownloadExtraInfo && authdir_mode_v3(options)) {
log_info(LD_CONFIG, "Authoritative directories always try to download "
"extra-info documents. Setting DownloadExtraInfo.");
options->DownloadExtraInfo = 1;
@@ -3565,6 +3575,10 @@ options_validate(or_options_t *old_options, or_options_t *options,
REJECT("PortForwarding is not compatible with Sandbox; at most one can "
"be set");
}
+ if (options->PortForwarding && options->NoExec) {
+ COMPLAIN("Both PortForwarding and NoExec are set; PortForwarding will "
+ "be ignored.");
+ }
if (ensure_bandwidth_cap(&options->BandwidthRate,
"BandwidthRate", msg) < 0)
@@ -4003,7 +4017,7 @@ options_validate(or_options_t *old_options, or_options_t *options,
COMPLAIN("V3AuthVotingInterval does not divide evenly into 24 hours.");
}
- if (rend_config_services(options, 1) < 0)
+ if (hs_config_service_all(options, 1) < 0)
REJECT("Failed to configure rendezvous options. See logs for details.");
/* Parse client-side authorization for hidden services. */
@@ -4442,6 +4456,12 @@ options_transition_allowed(const or_options_t *old,
return -1;
}
+ if (old->NoExec && !new_val->NoExec) {
+ *msg = tor_strdup("While Tor is running, disabling "
+ "NoExec is not allowed.");
+ return -1;
+ }
+
if (sandbox_is_active()) {
#define SB_NOCHANGE_STR(opt) \
do { \
@@ -4928,6 +4948,9 @@ options_init_from_torrc(int argc, char **argv)
for (p_index = cmdline_only_options; p_index; p_index = p_index->next) {
if (!strcmp(p_index->key,"--keygen")) {
command = CMD_KEYGEN;
+ } else if (!strcmp(p_index->key, "--key-expiration")) {
+ command = CMD_KEY_EXPIRATION;
+ command_arg = p_index->value;
} else if (!strcmp(p_index->key,"--list-fingerprint")) {
command = CMD_LIST_FINGERPRINT;
} else if (!strcmp(p_index->key, "--hash-password")) {
@@ -5718,6 +5741,15 @@ parse_transport_line(const or_options_t *options,
goto err;
}
+ if (is_managed && options->NoExec) {
+ log_warn(LD_CONFIG,
+ "Managed proxies are not compatible with NoExec mode; ignoring."
+ "(%sTransportPlugin line was %s)",
+ server ? "Server" : "Client", escaped(line));
+ r = 0;
+ goto done;
+ }
+
if (is_managed) {
/* managed */
@@ -6252,8 +6284,9 @@ port_cfg_free(port_cfg_t *port)
/** Warn for every port in <b>ports</b> of type <b>listener_type</b> that is
* on a publicly routable address. */
static void
-warn_nonlocal_client_ports(const smartlist_t *ports, const char *portname,
- int listener_type)
+warn_nonlocal_client_ports(const smartlist_t *ports,
+ const char *portname,
+ const int listener_type)
{
SMARTLIST_FOREACH_BEGIN(ports, const port_cfg_t *, port) {
if (port->type != listener_type)
@@ -6938,7 +6971,8 @@ parse_ports(or_options_t *options, int validate_only,
options->SocksPort_lines,
"Socks", CONN_TYPE_AP_LISTENER,
"127.0.0.1", 9050,
- CL_PORT_WARN_NONLOCAL|CL_PORT_TAKES_HOSTNAMES|gw_flag) < 0) {
+ ((validate_only ? 0 : CL_PORT_WARN_NONLOCAL)
+ | CL_PORT_TAKES_HOSTNAMES | gw_flag)) < 0) {
*msg = tor_strdup("Invalid SocksPort configuration");
goto err;
}
diff --git a/src/or/config.h b/src/or/config.h
index 27aec7fe3d..3cfa7c4e5b 100644
--- a/src/or/config.h
+++ b/src/or/config.h
@@ -18,6 +18,10 @@
#define KERNEL_MAY_SUPPORT_IPFW
#endif
+/** Lowest allowable value for HeartbeatPeriod; if this is too low, we might
+ * expose more information than we're comfortable with. */
+#define MIN_HEARTBEAT_PERIOD (30*60)
+
MOCK_DECL(const char*, get_dirportfrontpage, (void));
MOCK_DECL(const or_options_t *, get_options, (void));
MOCK_DECL(or_options_t *, get_options_mutable, (void));
diff --git a/src/or/connection.c b/src/or/connection.c
index 4e890497e9..31a682387d 100644
--- a/src/or/connection.c
+++ b/src/or/connection.c
@@ -84,6 +84,7 @@
#include "geoip.h"
#include "main.h"
#include "hs_common.h"
+#include "hs_ident.h"
#include "nodelist.h"
#include "policies.h"
#include "reasons.h"
@@ -605,6 +606,7 @@ connection_free_(connection_t *conn)
}
if (CONN_IS_EDGE(conn)) {
rend_data_free(TO_EDGE_CONN(conn)->rend_data);
+ hs_ident_edge_conn_free(TO_EDGE_CONN(conn)->hs_ident);
}
if (conn->type == CONN_TYPE_CONTROL) {
control_connection_t *control_conn = TO_CONTROL_CONN(conn);
@@ -636,6 +638,7 @@ connection_free_(connection_t *conn)
}
rend_data_free(dir_conn->rend_data);
+ hs_ident_dir_conn_free(dir_conn->hs_ident);
if (dir_conn->guard_state) {
/* Cancel before freeing, if it's still there. */
entry_guard_cancel(&dir_conn->guard_state);
@@ -4099,6 +4102,27 @@ connection_write_to_buf_impl_,(const char *string, size_t len,
}
}
+#define CONN_GET_ALL_TEMPLATE(var, test) \
+ STMT_BEGIN \
+ smartlist_t *conns = get_connection_array(); \
+ smartlist_t *ret_conns = smartlist_new(); \
+ SMARTLIST_FOREACH_BEGIN(conns, connection_t *, var) { \
+ if (var && (test) && !var->marked_for_close) \
+ smartlist_add(ret_conns, var); \
+ } SMARTLIST_FOREACH_END(var); \
+ return ret_conns; \
+ STMT_END
+
+/* Return a list of connections that aren't close and matches the given state.
+ * The returned list can be empty and must be freed using smartlist_free().
+ * The caller does NOT have owernship of the objects in the list so it must
+ * not free them nor reference them as they can disapear. */
+smartlist_t *
+connection_list_by_type_state(int type, int state)
+{
+ CONN_GET_ALL_TEMPLATE(conn, (conn->type == type && conn->state == state));
+}
+
/** Return a connection_t * from get_connection_array() that satisfies test on
* var, and that is not marked for close. */
#define CONN_GET_TEMPLATE(var, test) \
diff --git a/src/or/connection.h b/src/or/connection.h
index 36e45aef38..0bcf0ccdce 100644
--- a/src/or/connection.h
+++ b/src/or/connection.h
@@ -182,6 +182,7 @@ MOCK_DECL(connection_t *,connection_get_by_type_addr_port_purpose,(int type,
connection_t *connection_get_by_type_state(int type, int state);
connection_t *connection_get_by_type_state_rendquery(int type, int state,
const char *rendquery);
+smartlist_t *connection_list_by_type_state(int type, int state);
smartlist_t *connection_dir_list_by_purpose_and_resource(
int purpose,
const char *resource);
diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c
index 8480a35458..a98b32450b 100644
--- a/src/or/connection_edge.c
+++ b/src/or/connection_edge.c
@@ -76,6 +76,9 @@
#include "dirserv.h"
#include "hibernate.h"
#include "hs_common.h"
+#include "hs_cache.h"
+#include "hs_client.h"
+#include "hs_circuit.h"
#include "main.h"
#include "nodelist.h"
#include "policies.h"
@@ -152,7 +155,9 @@ connection_mark_unattached_ap_,(entry_connection_t *conn, int endreason,
* but we should fix it someday anyway. */
if ((edge_conn->on_circuit != NULL || edge_conn->edge_has_sent_end) &&
connection_edge_is_rendezvous_stream(edge_conn)) {
- rend_client_note_connection_attempt_ended(edge_conn->rend_data);
+ if (edge_conn->rend_data) {
+ rend_client_note_connection_attempt_ended(edge_conn->rend_data);
+ }
}
if (base_conn->marked_for_close) {
@@ -1391,6 +1396,180 @@ connection_ap_handshake_rewrite(entry_connection_t *conn,
}
}
+/** We just received a SOCKS request in <b>conn</b> to an onion address of type
+ * <b>addresstype</b>. Start connecting to the onion service. */
+static int
+connection_ap_handle_onion(entry_connection_t *conn,
+ socks_request_t *socks,
+ origin_circuit_t *circ,
+ hostname_type_t addresstype)
+{
+ time_t now = approx_time();
+ connection_t *base_conn = ENTRY_TO_CONN(conn);
+
+ /* If .onion address requests are disabled, refuse the request */
+ if (!conn->entry_cfg.onion_traffic) {
+ log_warn(LD_APP, "Onion address %s requested from a port with .onion "
+ "disabled", safe_str_client(socks->address));
+ connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
+ return -1;
+ }
+
+ /* Check whether it's RESOLVE or RESOLVE_PTR. We don't handle those
+ * for hidden service addresses. */
+ if (SOCKS_COMMAND_IS_RESOLVE(socks->command)) {
+ /* if it's a resolve request, fail it right now, rather than
+ * building all the circuits and then realizing it won't work. */
+ log_warn(LD_APP,
+ "Resolve requests to hidden services not allowed. Failing.");
+ connection_ap_handshake_socks_resolved(conn,RESOLVED_TYPE_ERROR,
+ 0,NULL,-1,TIME_MAX);
+ connection_mark_unattached_ap(conn,
+ END_STREAM_REASON_SOCKSPROTOCOL |
+ END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED);
+ return -1;
+ }
+
+ /* If we were passed a circuit, then we need to fail. .onion addresses
+ * only work when we launch our own circuits for now. */
+ if (circ) {
+ log_warn(LD_CONTROL, "Attachstream to a circuit is not "
+ "supported for .onion addresses currently. Failing.");
+ connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
+ return -1;
+ }
+
+ /* Interface: Regardless of HS version after the block below we should have
+ set onion_address, rend_cache_lookup_result, and descriptor_is_usable. */
+ const char *onion_address = NULL;
+ int rend_cache_lookup_result = -ENOENT;
+ int descriptor_is_usable = 0;
+
+ if (addresstype == ONION_V2_HOSTNAME) { /* it's a v2 hidden service */
+ rend_cache_entry_t *entry = NULL;
+ /* Look up if we have client authorization configured for this hidden
+ * service. If we do, associate it with the rend_data. */
+ rend_service_authorization_t *client_auth =
+ rend_client_lookup_service_authorization(socks->address);
+
+ const uint8_t *cookie = NULL;
+ rend_auth_type_t auth_type = REND_NO_AUTH;
+ if (client_auth) {
+ log_info(LD_REND, "Using previously configured client authorization "
+ "for hidden service request.");
+ auth_type = client_auth->auth_type;
+ cookie = client_auth->descriptor_cookie;
+ }
+
+ /* Fill in the rend_data field so we can start doing a connection to
+ * a hidden service. */
+ rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data =
+ rend_data_client_create(socks->address, NULL, (char *) cookie,
+ auth_type);
+ if (rend_data == NULL) {
+ return -1;
+ }
+ onion_address = rend_data_get_address(rend_data);
+ log_info(LD_REND,"Got a hidden service request for ID '%s'",
+ safe_str_client(onion_address));
+
+ rend_cache_lookup_result = rend_cache_lookup_entry(onion_address,-1,
+ &entry);
+ if (!rend_cache_lookup_result && entry) {
+ descriptor_is_usable = rend_client_any_intro_points_usable(entry);
+ }
+ } else { /* it's a v3 hidden service */
+ tor_assert(addresstype == ONION_V3_HOSTNAME);
+ const hs_descriptor_t *cached_desc = NULL;
+ int retval;
+ /* Create HS conn identifier with HS pubkey */
+ hs_ident_edge_conn_t *hs_conn_ident =
+ tor_malloc_zero(sizeof(hs_ident_edge_conn_t));
+
+ retval = hs_parse_address(socks->address, &hs_conn_ident->identity_pk,
+ NULL, NULL);
+ if (retval < 0) {
+ log_warn(LD_GENERAL, "failed to parse hs address");
+ tor_free(hs_conn_ident);
+ return -1;
+ }
+ ENTRY_TO_EDGE_CONN(conn)->hs_ident = hs_conn_ident;
+
+ onion_address = socks->address;
+
+ /* Check the v3 desc cache */
+ cached_desc = hs_cache_lookup_as_client(&hs_conn_ident->identity_pk);
+ if (cached_desc) {
+ rend_cache_lookup_result = 0;
+ descriptor_is_usable =
+ hs_client_any_intro_points_usable(&hs_conn_ident->identity_pk,
+ cached_desc);
+ log_info(LD_GENERAL, "Found %s descriptor in cache for %s. %s.",
+ (descriptor_is_usable) ? "usable" : "unusable",
+ safe_str_client(onion_address),
+ (descriptor_is_usable) ? "Not fetching." : "Refecting.");
+ } else {
+ rend_cache_lookup_result = -ENOENT;
+ }
+ }
+
+ /* Lookup the given onion address. If invalid, stop right now.
+ * Otherwise, we might have it in the cache or not. */
+ unsigned int refetch_desc = 0;
+ if (rend_cache_lookup_result < 0) {
+ switch (-rend_cache_lookup_result) {
+ case EINVAL:
+ /* We should already have rejected this address! */
+ log_warn(LD_BUG,"Invalid service name '%s'",
+ safe_str_client(onion_address));
+ connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
+ return -1;
+ case ENOENT:
+ /* We didn't have this; we should look it up. */
+ log_info(LD_REND, "No descriptor found in our cache for %s. Fetching.",
+ safe_str_client(onion_address));
+ refetch_desc = 1;
+ break;
+ default:
+ log_warn(LD_BUG, "Unknown cache lookup error %d",
+ rend_cache_lookup_result);
+ return -1;
+ }
+ }
+
+ /* Help predict that we'll want to do hidden service circuits in the
+ * future. We're not sure if it will need a stable circuit yet, but
+ * we know we'll need *something*. */
+ rep_hist_note_used_internal(now, 0, 1);
+
+ /* Now we have a descriptor but is it usable or not? If not, refetch.
+ * Also, a fetch could have been requested if the onion address was not
+ * found in the cache previously. */
+ if (refetch_desc || !descriptor_is_usable) {
+ edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn);
+ connection_ap_mark_as_non_pending_circuit(conn);
+ base_conn->state = AP_CONN_STATE_RENDDESC_WAIT;
+ if (addresstype == ONION_V2_HOSTNAME) {
+ tor_assert(edge_conn->rend_data);
+ rend_client_refetch_v2_renddesc(edge_conn->rend_data);
+ } else {
+ tor_assert(addresstype == ONION_V3_HOSTNAME);
+ tor_assert(edge_conn->hs_ident);
+ hs_client_refetch_hsdesc(&edge_conn->hs_ident->identity_pk);
+ }
+ return 0;
+ }
+
+ /* We have the descriptor! So launch a connection to the HS. */
+ base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT;
+ log_info(LD_REND, "Descriptor is here. Great.");
+
+ /* We'll try to attach it at the next event loop, or whenever
+ * we call connection_ap_attach_pending() */
+ connection_ap_mark_as_pending_circuit(conn);
+ return 0;
+}
+
/** Connection <b>conn</b> just finished its socks handshake, or the
* controller asked us to take care of it. If <b>circ</b> is defined,
* then that's where we'll want to attach it. Otherwise we have to
@@ -1557,7 +1736,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
}
/* Now, we handle everything that isn't a .onion address. */
- if (addresstype != ONION_HOSTNAME) {
+ if (addresstype != ONION_V2_HOSTNAME && addresstype != ONION_V3_HOSTNAME) {
/* Not a hidden-service request. It's either a hostname or an IP,
* possibly with a .exit that we stripped off. We're going to check
* if we're allowed to connect/resolve there, and then launch the
@@ -1835,116 +2014,10 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
return 0;
} else {
/* If we get here, it's a request for a .onion address! */
+ tor_assert(addresstype == ONION_V2_HOSTNAME ||
+ addresstype == ONION_V3_HOSTNAME);
tor_assert(!automap);
-
- /* If .onion address requests are disabled, refuse the request */
- if (!conn->entry_cfg.onion_traffic) {
- log_warn(LD_APP, "Onion address %s requested from a port with .onion "
- "disabled", safe_str_client(socks->address));
- connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
- return -1;
- }
-
- /* Check whether it's RESOLVE or RESOLVE_PTR. We don't handle those
- * for hidden service addresses. */
- if (SOCKS_COMMAND_IS_RESOLVE(socks->command)) {
- /* if it's a resolve request, fail it right now, rather than
- * building all the circuits and then realizing it won't work. */
- log_warn(LD_APP,
- "Resolve requests to hidden services not allowed. Failing.");
- connection_ap_handshake_socks_resolved(conn,RESOLVED_TYPE_ERROR,
- 0,NULL,-1,TIME_MAX);
- connection_mark_unattached_ap(conn,
- END_STREAM_REASON_SOCKSPROTOCOL |
- END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED);
- return -1;
- }
-
- /* If we were passed a circuit, then we need to fail. .onion addresses
- * only work when we launch our own circuits for now. */
- if (circ) {
- log_warn(LD_CONTROL, "Attachstream to a circuit is not "
- "supported for .onion addresses currently. Failing.");
- connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
- return -1;
- }
-
- /* Look up if we have client authorization configured for this hidden
- * service. If we do, associate it with the rend_data. */
- rend_service_authorization_t *client_auth =
- rend_client_lookup_service_authorization(socks->address);
-
- const uint8_t *cookie = NULL;
- rend_auth_type_t auth_type = REND_NO_AUTH;
- if (client_auth) {
- log_info(LD_REND, "Using previously configured client authorization "
- "for hidden service request.");
- auth_type = client_auth->auth_type;
- cookie = client_auth->descriptor_cookie;
- }
-
- /* Fill in the rend_data field so we can start doing a connection to
- * a hidden service. */
- rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data =
- rend_data_client_create(socks->address, NULL, (char *) cookie,
- auth_type);
- if (rend_data == NULL) {
- return -1;
- }
- const char *onion_address = rend_data_get_address(rend_data);
- log_info(LD_REND,"Got a hidden service request for ID '%s'",
- safe_str_client(onion_address));
-
- /* Lookup the given onion address. If invalid, stop right now.
- * Otherwise, we might have it in the cache or not. */
- unsigned int refetch_desc = 0;
- rend_cache_entry_t *entry = NULL;
- const int rend_cache_lookup_result =
- rend_cache_lookup_entry(onion_address, -1, &entry);
- if (rend_cache_lookup_result < 0) {
- switch (-rend_cache_lookup_result) {
- case EINVAL:
- /* We should already have rejected this address! */
- log_warn(LD_BUG,"Invalid service name '%s'",
- safe_str_client(onion_address));
- connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
- return -1;
- case ENOENT:
- /* We didn't have this; we should look it up. */
- refetch_desc = 1;
- break;
- default:
- log_warn(LD_BUG, "Unknown cache lookup error %d",
- rend_cache_lookup_result);
- return -1;
- }
- }
-
- /* Help predict that we'll want to do hidden service circuits in the
- * future. We're not sure if it will need a stable circuit yet, but
- * we know we'll need *something*. */
- rep_hist_note_used_internal(now, 0, 1);
-
- /* Now we have a descriptor but is it usable or not? If not, refetch.
- * Also, a fetch could have been requested if the onion address was not
- * found in the cache previously. */
- if (refetch_desc || !rend_client_any_intro_points_usable(entry)) {
- connection_ap_mark_as_non_pending_circuit(conn);
- base_conn->state = AP_CONN_STATE_RENDDESC_WAIT;
- log_info(LD_REND, "Unknown descriptor %s. Fetching.",
- safe_str_client(onion_address));
- rend_client_refetch_v2_renddesc(rend_data);
- return 0;
- }
-
- /* We have the descriptor! So launch a connection to the HS. */
- base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT;
- log_info(LD_REND, "Descriptor is here. Great.");
-
- /* We'll try to attach it at the next event loop, or whenever
- * we call connection_ap_attach_pending() */
- connection_ap_mark_as_pending_circuit(conn);
- return 0;
+ return connection_ap_handle_onion(conn, socks, circ, addresstype);
}
return 0; /* unreached but keeps the compiler happy */
@@ -2455,8 +2528,8 @@ connection_ap_get_begincell_flags(entry_connection_t *ap_conn)
*
* If ap_conn is broken, mark it for close and return -1. Else return 0.
*/
-int
-connection_ap_handshake_send_begin(entry_connection_t *ap_conn)
+MOCK_IMPL(int,
+connection_ap_handshake_send_begin,(entry_connection_t *ap_conn))
{
char payload[CELL_PAYLOAD_SIZE];
int payload_len;
@@ -3007,7 +3080,7 @@ connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply,
* <0 and set *<b>end_reason_out</b> to the end reason we should send back to
* the client.
*
- * Return -1 in the case where want to send a RELAY_END cell, and < -1 when
+ * Return -1 in the case where we want to send a RELAY_END cell, and < -1 when
* we don't.
**/
STATIC int
@@ -3066,6 +3139,88 @@ begin_cell_parse(const cell_t *cell, begin_cell_t *bcell,
return 0;
}
+/** For the given <b>circ</b> and the edge connection <b>conn</b>, setup the
+ * connection, attach it to the circ and connect it. Return 0 on success
+ * or END_CIRC_AT_ORIGIN if we can't find the requested hidden service port
+ * where the caller should close the circuit. */
+static int
+handle_hs_exit_conn(circuit_t *circ, edge_connection_t *conn)
+{
+ int ret;
+ origin_circuit_t *origin_circ;
+
+ assert_circuit_ok(circ);
+ tor_assert(circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED);
+ tor_assert(conn);
+
+ log_debug(LD_REND, "Connecting the hidden service rendezvous circuit "
+ "to the service destination.");
+
+ origin_circ = TO_ORIGIN_CIRCUIT(circ);
+ conn->base_.address = tor_strdup("(rendezvous)");
+ conn->base_.state = EXIT_CONN_STATE_CONNECTING;
+
+ /* The circuit either has an hs identifier for v3+ or a rend_data for legacy
+ * service. */
+ if (origin_circ->rend_data) {
+ conn->rend_data = rend_data_dup(origin_circ->rend_data);
+ tor_assert(connection_edge_is_rendezvous_stream(conn));
+ ret = rend_service_set_connection_addr_port(conn, origin_circ);
+ } else if (origin_circ->hs_ident) {
+ /* Setup the identifier to be the one for the circuit service. */
+ conn->hs_ident =
+ hs_ident_edge_conn_new(&origin_circ->hs_ident->identity_pk);
+ tor_assert(connection_edge_is_rendezvous_stream(conn));
+ ret = hs_service_set_conn_addr_port(origin_circ, conn);
+ } else {
+ /* We should never get here if the circuit's purpose is rendezvous. */
+ tor_assert_nonfatal_unreached();
+ return -1;
+ }
+ if (ret < 0) {
+ log_info(LD_REND, "Didn't find rendezvous service (addr%s, port %d)",
+ fmt_addr(&TO_CONN(conn)->addr), TO_CONN(conn)->port);
+ /* Send back reason DONE because we want to make hidden service port
+ * scanning harder thus instead of returning that the exit policy
+ * didn't match, which makes it obvious that the port is closed,
+ * return DONE and kill the circuit. That way, a user (malicious or
+ * not) needs one circuit per bad port unless it matches the policy of
+ * the hidden service. */
+ relay_send_end_cell_from_edge(conn->stream_id, circ,
+ END_STREAM_REASON_DONE,
+ origin_circ->cpath->prev);
+ connection_free(TO_CONN(conn));
+
+ /* Drop the circuit here since it might be someone deliberately
+ * scanning the hidden service ports. Note that this mitigates port
+ * scanning by adding more work on the attacker side to successfully
+ * scan but does not fully solve it. */
+ if (ret < -1) {
+ return END_CIRC_AT_ORIGIN;
+ } else {
+ return 0;
+ }
+ }
+
+ /* Link the circuit and the connection crypt path. */
+ conn->cpath_layer = origin_circ->cpath->prev;
+
+ /* Add it into the linked list of p_streams on this circuit */
+ conn->next_stream = origin_circ->p_streams;
+ origin_circ->p_streams = conn;
+ conn->on_circuit = circ;
+ assert_circuit_ok(circ);
+
+ hs_inc_rdv_stream_counter(origin_circ);
+
+ /* Connect tor to the hidden service destination. */
+ connection_exit_connect(conn);
+
+ /* For path bias: This circuit was used successfully */
+ pathbias_mark_use_success(origin_circ);
+ return 0;
+}
+
/** A relay 'begin' or 'begin_dir' cell has arrived, and either we are
* an exit hop for the circuit, or we are the origin and it is a
* rendezvous begin.
@@ -3217,58 +3372,10 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
n_stream->deliver_window = STREAMWINDOW_START;
if (circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED) {
- tor_assert(origin_circ);
- log_info(LD_REND,"begin is for rendezvous. configuring stream.");
- n_stream->base_.address = tor_strdup("(rendezvous)");
- n_stream->base_.state = EXIT_CONN_STATE_CONNECTING;
- n_stream->rend_data = rend_data_dup(origin_circ->rend_data);
- tor_assert(connection_edge_is_rendezvous_stream(n_stream));
- assert_circuit_ok(circ);
-
- const int r = rend_service_set_connection_addr_port(n_stream, origin_circ);
- if (r < 0) {
- log_info(LD_REND,"Didn't find rendezvous service (port %d)",
- n_stream->base_.port);
- /* Send back reason DONE because we want to make hidden service port
- * scanning harder thus instead of returning that the exit policy
- * didn't match, which makes it obvious that the port is closed,
- * return DONE and kill the circuit. That way, a user (malicious or
- * not) needs one circuit per bad port unless it matches the policy of
- * the hidden service. */
- relay_send_end_cell_from_edge(rh.stream_id, circ,
- END_STREAM_REASON_DONE,
- layer_hint);
- connection_free(TO_CONN(n_stream));
- tor_free(address);
-
- /* Drop the circuit here since it might be someone deliberately
- * scanning the hidden service ports. Note that this mitigates port
- * scanning by adding more work on the attacker side to successfully
- * scan but does not fully solve it. */
- if (r < -1)
- return END_CIRC_AT_ORIGIN;
- else
- return 0;
- }
- assert_circuit_ok(circ);
- log_debug(LD_REND,"Finished assigning addr/port");
- n_stream->cpath_layer = origin_circ->cpath->prev; /* link it */
-
- /* add it into the linked list of p_streams on this circuit */
- n_stream->next_stream = origin_circ->p_streams;
- n_stream->on_circuit = circ;
- origin_circ->p_streams = n_stream;
- assert_circuit_ok(circ);
-
- origin_circ->rend_data->nr_streams++;
-
- connection_exit_connect(n_stream);
-
- /* For path bias: This circuit was used successfully */
- pathbias_mark_use_success(origin_circ);
-
tor_free(address);
- return 0;
+ /* We handle this circuit and stream in this function for all supported
+ * hidden service version. */
+ return handle_hs_exit_conn(circ, n_stream);
}
tor_strlower(address);
n_stream->base_.address = address;
@@ -3566,8 +3673,12 @@ int
connection_edge_is_rendezvous_stream(const edge_connection_t *conn)
{
tor_assert(conn);
- if (conn->rend_data)
+ /* It should not be possible to set both of these structs */
+ tor_assert_nonfatal(!(conn->rend_data && conn->hs_ident));
+
+ if (conn->rend_data || conn->hs_ident) {
return 1;
+ }
return 0;
}
@@ -3640,10 +3751,12 @@ connection_ap_can_use_exit(const entry_connection_t *conn,
}
/** If address is of the form "y.onion" with a well-formed handle y:
- * Put a NUL after y, lower-case it, and return ONION_HOSTNAME.
+ * Put a NUL after y, lower-case it, and return ONION_V2_HOSTNAME or
+ * ONION_V3_HOSTNAME depending on the HS version.
*
* If address is of the form "x.y.onion" with a well-formed handle x:
- * Drop "x.", put a NUL after y, lower-case it, and return ONION_HOSTNAME.
+ * Drop "x.", put a NUL after y, lower-case it, and return
+ * ONION_V2_HOSTNAME or ONION_V3_HOSTNAME depending on the HS version.
*
* If address is of the form "y.onion" with a badly-formed handle y:
* Return BAD_HOSTNAME and log a message.
@@ -3659,7 +3772,7 @@ parse_extended_hostname(char *address)
{
char *s;
char *q;
- char query[REND_SERVICE_ID_LEN_BASE32+1];
+ char query[HS_SERVICE_ADDR_LEN_BASE32+1];
s = strrchr(address,'.');
if (!s)
@@ -3679,14 +3792,17 @@ parse_extended_hostname(char *address)
goto failed; /* reject sub-domain, as DNS does */
}
q = (NULL == q) ? address : q + 1;
- if (strlcpy(query, q, REND_SERVICE_ID_LEN_BASE32+1) >=
- REND_SERVICE_ID_LEN_BASE32+1)
+ if (strlcpy(query, q, HS_SERVICE_ADDR_LEN_BASE32+1) >=
+ HS_SERVICE_ADDR_LEN_BASE32+1)
goto failed;
if (q != address) {
memmove(address, q, strlen(q) + 1 /* also get \0 */);
}
- if (rend_valid_service_id(query)) {
- return ONION_HOSTNAME; /* success */
+ if (rend_valid_v2_service_id(query)) {
+ return ONION_V2_HOSTNAME; /* success */
+ }
+ if (hs_address_is_valid(query)) {
+ return ONION_V3_HOSTNAME;
}
failed:
/* otherwise, return to previous state and return 0 */
diff --git a/src/or/connection_edge.h b/src/or/connection_edge.h
index e4780b3c7d..914238fc1f 100644
--- a/src/or/connection_edge.h
+++ b/src/or/connection_edge.h
@@ -33,7 +33,8 @@ int connection_edge_finished_connecting(edge_connection_t *conn);
void connection_ap_about_to_close(entry_connection_t *edge_conn);
void connection_exit_about_to_close(edge_connection_t *edge_conn);
-int connection_ap_handshake_send_begin(entry_connection_t *ap_conn);
+MOCK_DECL(int,
+ connection_ap_handshake_send_begin,(entry_connection_t *ap_conn));
int connection_ap_handshake_send_resolve(entry_connection_t *ap_conn);
entry_connection_t *connection_ap_make_link(connection_t *partner,
@@ -97,7 +98,8 @@ int connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
/** Possible return values for parse_extended_hostname. */
typedef enum hostname_type_t {
- NORMAL_HOSTNAME, ONION_HOSTNAME, EXIT_HOSTNAME, BAD_HOSTNAME
+ NORMAL_HOSTNAME, ONION_V2_HOSTNAME, ONION_V3_HOSTNAME,
+ EXIT_HOSTNAME, BAD_HOSTNAME
} hostname_type_t;
hostname_type_t parse_extended_hostname(char *address);
diff --git a/src/or/connection_or.c b/src/or/connection_or.c
index 753148291c..fc304e6f18 100644
--- a/src/or/connection_or.c
+++ b/src/or/connection_or.c
@@ -472,7 +472,7 @@ var_cell_pack_header(const var_cell_t *cell, char *hdr_out, int wide_circ_ids)
var_cell_t *
var_cell_new(uint16_t payload_len)
{
- size_t size = STRUCT_OFFSET(var_cell_t, payload) + payload_len;
+ size_t size = offsetof(var_cell_t, payload) + payload_len;
var_cell_t *cell = tor_malloc_zero(size);
cell->payload_len = payload_len;
cell->command = 0;
@@ -491,7 +491,7 @@ var_cell_copy(const var_cell_t *src)
size_t size = 0;
if (src != NULL) {
- size = STRUCT_OFFSET(var_cell_t, payload) + src->payload_len;
+ size = offsetof(var_cell_t, payload) + src->payload_len;
copy = tor_malloc_zero(size);
copy->payload_len = src->payload_len;
copy->command = src->command;
@@ -1369,7 +1369,6 @@ connection_tls_start_handshake,(or_connection_t *conn, int receiving))
connection_start_reading(TO_CONN(conn));
log_debug(LD_HANDSHAKE,"starting TLS handshake on fd "TOR_SOCKET_T_FORMAT,
conn->base_.s);
- note_crypto_pk_op(receiving ? TLS_HANDSHAKE_S : TLS_HANDSHAKE_C);
if (connection_tls_continue_handshake(conn) < 0)
return -1;
@@ -1549,7 +1548,10 @@ connection_or_check_valid_tls_handshake(or_connection_t *conn,
}
if (identity_rcvd) {
- crypto_pk_get_digest(identity_rcvd, digest_rcvd_out);
+ if (crypto_pk_get_digest(identity_rcvd, digest_rcvd_out) < 0) {
+ crypto_pk_free(identity_rcvd);
+ return -1;
+ }
} else {
memset(digest_rcvd_out, 0, DIGEST_LEN);
}
diff --git a/src/or/consdiffmgr.c b/src/or/consdiffmgr.c
index 67a5d0b3c5..928fc26f54 100644
--- a/src/or/consdiffmgr.c
+++ b/src/or/consdiffmgr.c
@@ -1156,6 +1156,7 @@ consdiffmgr_ensure_space_for_files(int n)
if (++n_marked >= n_to_remove)
break;
} SMARTLIST_FOREACH_END(ent);
+ smartlist_free(objects);
consensus_cache_delete_pending(cache, 1);
if (BUG(n_marked < n_to_remove))
diff --git a/src/or/control.c b/src/or/control.c
index 9bcf1ee364..bc173a6e1c 100644
--- a/src/or/control.c
+++ b/src/or/control.c
@@ -60,6 +60,7 @@
#include "hibernate.h"
#include "hs_common.h"
#include "main.h"
+#include "microdesc.h"
#include "networkstatus.h"
#include "nodelist.h"
#include "policies.h"
@@ -367,16 +368,23 @@ connection_write_str_to_buf(const char *s, control_connection_t *conn)
STATIC size_t
write_escaped_data(const char *data, size_t len, char **out)
{
- size_t sz_out = len+8;
+ tor_assert(len < SIZE_MAX - 9);
+ size_t sz_out = len+8+1;
char *outp;
const char *start = data, *end;
- int i;
+ size_t i;
int start_of_line;
- for (i=0; i<(int)len; ++i) {
- if (data[i]== '\n')
+ for (i=0; i < len; ++i) {
+ if (data[i] == '\n') {
sz_out += 2; /* Maybe add a CR; maybe add a dot. */
+ if (sz_out >= SIZE_T_CEILING) {
+ log_warn(LD_BUG, "Input to write_escaped_data was too long");
+ *out = tor_strdup(".\r\n");
+ return 3;
+ }
+ }
}
- *out = outp = tor_malloc(sz_out+1);
+ *out = outp = tor_malloc(sz_out);
end = data+len;
start_of_line = 1;
while (data < end) {
@@ -402,7 +410,8 @@ write_escaped_data(const char *data, size_t len, char **out)
*outp++ = '\r';
*outp++ = '\n';
*outp = '\0'; /* NUL-terminate just in case. */
- tor_assert((outp - *out) <= (int)sz_out);
+ tor_assert(outp >= *out);
+ tor_assert((size_t)(outp - *out) <= sz_out);
return outp - *out;
}
@@ -802,7 +811,7 @@ queued_events_flush_all(int force)
}
/** Libevent callback: Flushes pending events to controllers that are
- * interested in them */
+ * interested in them. */
static void
flush_queued_events_cb(evutil_socket_t fd, short what, void *arg)
{
@@ -1892,6 +1901,12 @@ getinfo_helper_dir(control_connection_t *control_conn,
const char *body = signed_descriptor_get_body(&ri->cache_info);
if (body)
*answer = tor_strndup(body, ri->cache_info.signed_descriptor_len);
+ } else if (! we_fetch_router_descriptors(get_options())) {
+ /* Descriptors won't be available, provide proper error */
+ *errmsg = "We fetch microdescriptors, not router "
+ "descriptors. You'll need to use md/id/* "
+ "instead of desc/id/*.";
+ return 0;
}
} else if (!strcmpstart(question, "desc/name/")) {
const routerinfo_t *ri = NULL;
@@ -1905,7 +1920,16 @@ getinfo_helper_dir(control_connection_t *control_conn,
const char *body = signed_descriptor_get_body(&ri->cache_info);
if (body)
*answer = tor_strndup(body, ri->cache_info.signed_descriptor_len);
+ } else if (! we_fetch_router_descriptors(get_options())) {
+ /* Descriptors won't be available, provide proper error */
+ *errmsg = "We fetch microdescriptors, not router "
+ "descriptors. You'll need to use md/name/* "
+ "instead of desc/name/*.";
+ return 0;
}
+ } else if (!strcmp(question, "desc/download-enabled")) {
+ int r = we_fetch_router_descriptors(get_options());
+ tor_asprintf(answer, "%d", !!r);
} else if (!strcmp(question, "desc/all-recent")) {
routerlist_t *routerlist = router_get_routerlist();
smartlist_t *sl = smartlist_new();
@@ -1991,6 +2015,9 @@ getinfo_helper_dir(control_connection_t *control_conn,
if (md && md->body) {
*answer = tor_strndup(md->body, md->bodylen);
}
+ } else if (!strcmp(question, "md/download-enabled")) {
+ int r = we_fetch_microdescriptors(get_options());
+ tor_asprintf(answer, "%d", !!r);
} else if (!strcmpstart(question, "desc-annotations/id/")) {
const routerinfo_t *ri = NULL;
const node_t *node =
@@ -2907,7 +2934,8 @@ getinfo_helper_sr(control_connection_t *control_conn,
* *<b>a</b>. If an internal error occurs, return -1 and optionally set
* *<b>error_out</b> to point to an error message to be delivered to the
* controller. On success, _or if the key is not recognized_, return 0. Do not
- * set <b>a</b> if the key is not recognized.
+ * set <b>a</b> if the key is not recognized but you may set <b>error_out</b>
+ * to improve the error message.
*/
typedef int (*getinfo_helper_t)(control_connection_t *,
const char *q, char **a,
@@ -3012,9 +3040,13 @@ static const getinfo_item_t getinfo_items[] = {
PREFIX("desc/name/", dir, "Router descriptors by nickname."),
ITEM("desc/all-recent", dir,
"All non-expired, non-superseded router descriptors."),
+ ITEM("desc/download-enabled", dir,
+ "Do we try to download router descriptors?"),
ITEM("desc/all-recent-extrainfo-hack", dir, NULL), /* Hack. */
PREFIX("md/id/", dir, "Microdescriptors by ID"),
PREFIX("md/name/", dir, "Microdescriptors by name"),
+ ITEM("md/download-enabled", dir,
+ "Do we try to download microdescriptors?"),
PREFIX("extra-info/digest/", dir, "Extra-info documents by digest."),
PREFIX("hs/client/desc/id", dir,
"Hidden Service descriptor in client's cache by onion."),
@@ -3162,7 +3194,7 @@ handle_control_getinfo(control_connection_t *conn, uint32_t len,
smartlist_t *questions = smartlist_new();
smartlist_t *answers = smartlist_new();
smartlist_t *unrecognized = smartlist_new();
- char *msg = NULL, *ans = NULL;
+ char *ans = NULL;
int i;
(void) len; /* body is NUL-terminated, so it's safe to ignore the length. */
@@ -3177,20 +3209,26 @@ handle_control_getinfo(control_connection_t *conn, uint32_t len,
goto done;
}
if (!ans) {
- smartlist_add(unrecognized, (char*)q);
+ if (errmsg) /* use provided error message */
+ smartlist_add_strdup(unrecognized, errmsg);
+ else /* use default error message */
+ smartlist_add_asprintf(unrecognized, "Unrecognized key \"%s\"", q);
} else {
smartlist_add_strdup(answers, q);
smartlist_add(answers, ans);
}
} SMARTLIST_FOREACH_END(q);
+
if (smartlist_len(unrecognized)) {
+ /* control-spec section 2.3, mid-reply '-' or end of reply ' ' */
for (i=0; i < smartlist_len(unrecognized)-1; ++i)
connection_printf_to_buf(conn,
- "552-Unrecognized key \"%s\"\r\n",
- (char*)smartlist_get(unrecognized, i));
+ "552-%s\r\n",
+ (char *)smartlist_get(unrecognized, i));
+
connection_printf_to_buf(conn,
- "552 Unrecognized key \"%s\"\r\n",
- (char*)smartlist_get(unrecognized, i));
+ "552 %s\r\n",
+ (char *)smartlist_get(unrecognized, i));
goto done;
}
@@ -3217,8 +3255,8 @@ handle_control_getinfo(control_connection_t *conn, uint32_t len,
smartlist_free(answers);
SMARTLIST_FOREACH(questions, char *, cp, tor_free(cp));
smartlist_free(questions);
+ SMARTLIST_FOREACH(unrecognized, char *, cp, tor_free(cp));
smartlist_free(unrecognized);
- tor_free(msg);
return 0;
}
@@ -4102,7 +4140,7 @@ handle_control_hsfetch(control_connection_t *conn, uint32_t len,
/* Extract the first argument (either HSAddress or DescID). */
arg1 = smartlist_get(args, 0);
/* Test if it's an HS address without the .onion part. */
- if (rend_valid_service_id(arg1)) {
+ if (rend_valid_v2_service_id(arg1)) {
hsaddress = arg1;
} else if (strcmpstart(arg1, v2_str) == 0 &&
rend_valid_descriptor_id(arg1 + v2_str_len) &&
@@ -4741,7 +4779,7 @@ handle_control_del_onion(control_connection_t *conn,
return 0;
const char *service_id = smartlist_get(args, 0);
- if (!rend_valid_service_id(service_id)) {
+ if (!rend_valid_v2_service_id(service_id)) {
connection_printf_to_buf(conn, "512 Malformed Onion Service id\r\n");
goto out;
}
@@ -4882,6 +4920,38 @@ peek_connection_has_control0_command(connection_t *conn)
return peek_buf_has_control0_command(conn->inbuf);
}
+static int
+peek_connection_has_http_command(connection_t *conn)
+{
+ return peek_buf_has_http_command(conn->inbuf);
+}
+
+static const char CONTROLPORT_IS_NOT_AN_HTTP_PROXY_MSG[] =
+ "HTTP/1.0 501 Tor ControlPort is not an HTTP proxy"
+ "\r\nContent-Type: text/html; charset=iso-8859-1\r\n\r\n"
+ "<html>\n"
+ "<head>\n"
+ "<title>Tor's ControlPort is not an HTTP proxy</title>\n"
+ "</head>\n"
+ "<body>\n"
+ "<h1>Tor's ControlPort is not an HTTP proxy</h1>\n"
+ "<p>\n"
+ "It appears you have configured your web browser to use Tor's control port"
+ " as an HTTP proxy.\n"
+ "This is not correct: Tor's default SOCKS proxy port is 9050.\n"
+ "Please configure your client accordingly.\n"
+ "</p>\n"
+ "<p>\n"
+ "See <a href=\"https://www.torproject.org/documentation.html\">"
+ "https://www.torproject.org/documentation.html</a> for more "
+ "information.\n"
+ "<!-- Plus this comment, to make the body response more than 512 bytes, so "
+ " IE will be willing to display it. Comment comment comment comment "
+ " comment comment comment comment comment comment comment comment.-->\n"
+ "</p>\n"
+ "</body>\n"
+ "</html>\n";
+
/** Called when data has arrived on a v1 control connection: Try to fetch
* commands from conn->inbuf, and execute them.
*/
@@ -4921,6 +4991,15 @@ connection_control_process_inbuf(control_connection_t *conn)
return 0;
}
+ /* If the user has the HTTP proxy port and the control port confused. */
+ if (conn->base_.state == CONTROL_CONN_STATE_NEEDAUTH &&
+ peek_connection_has_http_command(TO_CONN(conn))) {
+ connection_write_str_to_buf(CONTROLPORT_IS_NOT_AN_HTTP_PROXY_MSG, conn);
+ log_notice(LD_CONTROL, "Received HTTP request on ControlPort");
+ connection_mark_and_flush(TO_CONN(conn));
+ return 0;
+ }
+
again:
while (1) {
size_t last_idx;
diff --git a/src/or/cpuworker.c b/src/or/cpuworker.c
index f5fff2b331..d9371b3446 100644
--- a/src/or/cpuworker.c
+++ b/src/or/cpuworker.c
@@ -382,7 +382,7 @@ cpuworker_onion_handshake_replyfn(void *work_)
if (onionskin_answer(circ,
&rpl.created_cell,
- (const char*)rpl.keys,
+ (const char*)rpl.keys, sizeof(rpl.keys),
rpl.rend_auth_material) < 0) {
log_warn(LD_OR,"onionskin_answer failed. Closing.");
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
@@ -484,7 +484,7 @@ queue_pending_tasks(void)
if (!circ)
return;
- if (assign_onionskin_to_cpuworker(circ, onionskin))
+ if (assign_onionskin_to_cpuworker(circ, onionskin) < 0)
log_info(LD_OR,"assign_to_cpuworker failed. Ignoring.");
}
}
diff --git a/src/or/dircollate.c b/src/or/dircollate.c
index 172364c5f5..d34ebe8af5 100644
--- a/src/or/dircollate.c
+++ b/src/or/dircollate.c
@@ -53,7 +53,7 @@ ddmap_entry_free(ddmap_entry_t *e)
static ddmap_entry_t *
ddmap_entry_new(int n_votes)
{
- return tor_malloc_zero(STRUCT_OFFSET(ddmap_entry_t, vrs_lst) +
+ return tor_malloc_zero(offsetof(ddmap_entry_t, vrs_lst) +
sizeof(vote_routerstatus_t *) * n_votes);
}
diff --git a/src/or/directory.c b/src/or/directory.c
index 45fbd1dd33..57dfdd9cac 100644
--- a/src/or/directory.c
+++ b/src/or/directory.c
@@ -18,7 +18,6 @@
#include "consdiffmgr.h"
#include "control.h"
#include "compat.h"
-#define DIRECTORY_PRIVATE
#include "directory.h"
#include "dirserv.h"
#include "dirvote.h"
@@ -26,6 +25,7 @@
#include "geoip.h"
#include "hs_cache.h"
#include "hs_common.h"
+#include "hs_client.h"
#include "main.h"
#include "microdesc.h"
#include "networkstatus.h"
@@ -184,9 +184,12 @@ purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose,
case DIR_PURPOSE_FETCH_EXTRAINFO:
case DIR_PURPOSE_FETCH_MICRODESC:
return 0;
+ case DIR_PURPOSE_HAS_FETCHED_HSDESC:
case DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2:
case DIR_PURPOSE_UPLOAD_RENDDESC_V2:
case DIR_PURPOSE_FETCH_RENDDESC_V2:
+ case DIR_PURPOSE_FETCH_HSDESC:
+ case DIR_PURPOSE_UPLOAD_HSDESC:
return 1;
case DIR_PURPOSE_SERVER:
default:
@@ -245,6 +248,10 @@ dir_conn_purpose_to_string(int purpose)
return "hidden-service v2 descriptor fetch";
case DIR_PURPOSE_UPLOAD_RENDDESC_V2:
return "hidden-service v2 descriptor upload";
+ case DIR_PURPOSE_FETCH_HSDESC:
+ return "hidden-service descriptor fetch";
+ case DIR_PURPOSE_UPLOAD_HSDESC:
+ return "hidden-service descriptor upload";
case DIR_PURPOSE_FETCH_MICRODESC:
return "microdescriptor fetch";
}
@@ -1035,11 +1042,12 @@ struct directory_request_t {
size_t payload_len;
/** Value to send in an if-modified-since header, or 0 for none. */
time_t if_modified_since;
- /** Hidden-service-specific information */
+ /** Hidden-service-specific information v2. */
const rend_data_t *rend_query;
/** Extra headers to append to the request */
config_line_t *additional_headers;
- /** */
+ /** Hidden-service-specific information for v3+. */
+ const hs_ident_dir_conn_t *hs_ident;
/** Used internally to directory.c: gets informed when the attempt to
* connect to the directory succeeds or fails, if that attempt bears on the
* directory's usability as a directory guard. */
@@ -1119,6 +1127,7 @@ directory_request_new(uint8_t dir_purpose)
tor_assert(dir_purpose <= DIR_PURPOSE_MAX_);
tor_assert(dir_purpose != DIR_PURPOSE_SERVER);
tor_assert(dir_purpose != DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2);
+ tor_assert(dir_purpose != DIR_PURPOSE_HAS_FETCHED_HSDESC);
directory_request_t *result = tor_malloc_zero(sizeof(*result));
tor_addr_make_null(&result->or_addr_port.addr, AF_INET);
@@ -1269,6 +1278,34 @@ directory_request_set_rend_query(directory_request_t *req,
}
req->rend_query = query;
}
+/**
+ * Set an object containing HS connection identifier to be associated with
+ * this request. Note that only an alias to <b>ident</b> is stored, so the
+ * <b>ident</b> object must outlive the request.
+ */
+void
+directory_request_upload_set_hs_ident(directory_request_t *req,
+ const hs_ident_dir_conn_t *ident)
+{
+ if (ident) {
+ tor_assert(req->dir_purpose == DIR_PURPOSE_UPLOAD_HSDESC);
+ }
+ req->hs_ident = ident;
+}
+/**
+ * Set an object containing HS connection identifier to be associated with
+ * this fetch request. Note that only an alias to <b>ident</b> is stored, so
+ * the <b>ident</b> object must outlive the request.
+ */
+void
+directory_request_fetch_set_hs_ident(directory_request_t *req,
+ const hs_ident_dir_conn_t *ident)
+{
+ if (ident) {
+ tor_assert(req->dir_purpose == DIR_PURPOSE_FETCH_HSDESC);
+ }
+ req->hs_ident = ident;
+}
/** Set a static circuit_guard_state_t object to affliate with the request in
* <b>req</b>. This object will receive notification when the attempt to
* connect to the guard either succeeds or fails. */
@@ -1390,6 +1427,7 @@ directory_initiate_request,(directory_request_t *request))
const dir_indirection_t indirection = request->indirection;
const char *resource = request->resource;
const rend_data_t *rend_query = request->rend_query;
+ const hs_ident_dir_conn_t *hs_ident = request->hs_ident;
circuit_guard_state_t *guard_state = request->guard_state;
tor_assert(or_addr_port->port || dir_addr_port->port);
@@ -1477,8 +1515,16 @@ directory_initiate_request,(directory_request_t *request))
conn->dirconn_direct = !anonymized_connection;
/* copy rendezvous data, if any */
- if (rend_query)
+ if (rend_query) {
+ /* We can't have both v2 and v3+ identifier. */
+ tor_assert_nonfatal(!hs_ident);
conn->rend_data = rend_data_dup(rend_query);
+ }
+ if (hs_ident) {
+ /* We can't have both v2 and v3+ identifier. */
+ tor_assert_nonfatal(!rend_query);
+ conn->hs_ident = hs_ident_dir_conn_dup(hs_ident);
+ }
if (!anonymized_connection && !use_begindir) {
/* then we want to connect to dirport directly */
@@ -1830,12 +1876,25 @@ directory_send_command(dir_connection_t *conn,
httpcommand = "GET";
tor_asprintf(&url, "/tor/rendezvous2/%s", resource);
break;
+ case DIR_PURPOSE_FETCH_HSDESC:
+ tor_assert(resource);
+ tor_assert(strlen(resource) <= ED25519_BASE64_LEN);
+ tor_assert(!payload);
+ httpcommand = "GET";
+ tor_asprintf(&url, "/tor/hs/3/%s", resource);
+ break;
case DIR_PURPOSE_UPLOAD_RENDDESC_V2:
tor_assert(!resource);
tor_assert(payload);
httpcommand = "POST";
url = tor_strdup("/tor/rendezvous2/publish");
break;
+ case DIR_PURPOSE_UPLOAD_HSDESC:
+ tor_assert(resource);
+ tor_assert(payload);
+ httpcommand = "POST";
+ tor_asprintf(&url, "/tor/hs/%s/publish", resource);
+ break;
default:
tor_assert(0);
return;
@@ -2158,16 +2217,6 @@ load_downloaded_routers(const char *body, smartlist_t *which,
return added;
}
-/** A structure to hold arguments passed into each directory response
- * handler */
-typedef struct response_handler_args_t {
- int status_code;
- const char *reason;
- const char *body;
- size_t body_len;
- const char *headers;
-} response_handler_args_t;
-
static int handle_response_fetch_consensus(dir_connection_t *,
const response_handler_args_t *);
static int handle_response_fetch_certificate(dir_connection_t *,
@@ -2190,6 +2239,8 @@ static int handle_response_fetch_renddesc_v2(dir_connection_t *,
const response_handler_args_t *);
static int handle_response_upload_renddesc_v2(dir_connection_t *,
const response_handler_args_t *);
+static int handle_response_upload_hsdesc(dir_connection_t *,
+ const response_handler_args_t *);
static int
dir_client_decompress_response_body(char **bodyp, size_t *bodylenp,
@@ -2490,6 +2541,12 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
case DIR_PURPOSE_UPLOAD_RENDDESC_V2:
rv = handle_response_upload_renddesc_v2(conn, &args);
break;
+ case DIR_PURPOSE_UPLOAD_HSDESC:
+ rv = handle_response_upload_hsdesc(conn, &args);
+ break;
+ case DIR_PURPOSE_FETCH_HSDESC:
+ rv = handle_response_fetch_hsdesc_v3(conn, &args);
+ break;
default:
tor_assert_nonfatal_unreached();
rv = -1;
@@ -3035,6 +3092,60 @@ handle_response_upload_signatures(dir_connection_t *conn,
}
/**
+ * Handler function: processes a response to a request for a v3 hidden service
+ * descriptor.
+ **/
+STATIC int
+handle_response_fetch_hsdesc_v3(dir_connection_t *conn,
+ const response_handler_args_t *args)
+{
+ const int status_code = args->status_code;
+ const char *reason = args->reason;
+ const char *body = args->body;
+ const size_t body_len = args->body_len;
+
+ tor_assert(conn->hs_ident);
+
+ log_info(LD_REND,"Received v3 hsdesc (body size %d, status %d (%s))",
+ (int)body_len, status_code, escaped(reason));
+
+ switch (status_code) {
+ case 200:
+ /* We got something: Try storing it in the cache. */
+ if (hs_cache_store_as_client(body, &conn->hs_ident->identity_pk) < 0) {
+ log_warn(LD_REND, "Failed to store hidden service descriptor");
+ } else {
+ log_info(LD_REND, "Stored hidden service descriptor successfully.");
+ TO_CONN(conn)->purpose = DIR_PURPOSE_HAS_FETCHED_HSDESC;
+ hs_client_desc_has_arrived(conn->hs_ident);
+ }
+ break;
+ case 404:
+ /* Not there. We'll retry when connection_about_to_close_connection()
+ * tries to clean this conn up. */
+ log_info(LD_REND, "Fetching hidden service v3 descriptor not found: "
+ "Retrying at another directory.");
+ /* TODO: Inform the control port */
+ break;
+ case 400:
+ log_warn(LD_REND, "Fetching v3 hidden service descriptor failed: "
+ "http status 400 (%s). Dirserver didn't like our "
+ "query? Retrying at another directory.",
+ escaped(reason));
+ break;
+ default:
+ log_warn(LD_REND, "Fetching v3 hidden service descriptor failed: "
+ "http status %d (%s) response unexpected from HSDir server "
+ "'%s:%d'. Retrying at another directory.",
+ status_code, escaped(reason), TO_CONN(conn)->address,
+ TO_CONN(conn)->port);
+ break;
+ }
+
+ return 0;
+}
+
+/**
* Handler function: processes a response to a request for a v2 hidden service
* descriptor.
**/
@@ -3181,6 +3292,52 @@ handle_response_upload_renddesc_v2(dir_connection_t *conn,
return 0;
}
+/**
+ * Handler function: processes a response to a POST request to upload an
+ * hidden service descriptor.
+ **/
+static int
+handle_response_upload_hsdesc(dir_connection_t *conn,
+ const response_handler_args_t *args)
+{
+ const int status_code = args->status_code;
+ const char *reason = args->reason;
+
+ tor_assert(conn);
+ tor_assert(conn->base_.purpose == DIR_PURPOSE_UPLOAD_HSDESC);
+
+ log_info(LD_REND, "Uploaded hidden service descriptor (status %d "
+ "(%s))",
+ status_code, escaped(reason));
+ /* For this directory response, it MUST have an hidden service identifier on
+ * this connection. */
+ tor_assert(conn->hs_ident);
+ switch (status_code) {
+ case 200:
+ log_info(LD_REND, "Uploading hidden service descriptor: "
+ "finished with status 200 (%s)", escaped(reason));
+ /* XXX: Trigger control event. */
+ break;
+ case 400:
+ log_warn(LD_REND, "Uploading hidden service descriptor: http "
+ "status 400 (%s) response from dirserver "
+ "'%s:%d'. Malformed hidden service descriptor?",
+ escaped(reason), conn->base_.address, conn->base_.port);
+ /* XXX: Trigger control event. */
+ break;
+ default:
+ log_warn(LD_REND, "Uploading hidden service descriptor: http "
+ "status %d (%s) response unexpected (server "
+ "'%s:%d').",
+ status_code, escaped(reason), conn->base_.address,
+ conn->base_.port);
+ /* XXX: Trigger control event. */
+ break;
+ }
+
+ return 0;
+}
+
/** Called when a directory connection reaches EOF. */
int
connection_dir_reached_eof(dir_connection_t *conn)
@@ -3252,6 +3409,33 @@ connection_dir_process_inbuf(dir_connection_t *conn)
return 0;
}
+/** We are closing a dir connection: If <b>dir_conn</b> is a dir connection
+ * that tried to fetch an HS descriptor, check if it successfuly fetched it,
+ * or if we need to try again. */
+static void
+refetch_hsdesc_if_needed(dir_connection_t *dir_conn)
+{
+ connection_t *conn = TO_CONN(dir_conn);
+
+ /* If we were trying to fetch a v2 rend desc and did not succeed, retry as
+ * needed. (If a fetch is successful, the connection state is changed to
+ * DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2 or DIR_PURPOSE_HAS_FETCHED_HSDESC to
+ * mark that refetching is unnecessary.) */
+ if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 &&
+ dir_conn->rend_data &&
+ rend_valid_v2_service_id(
+ rend_data_get_address(dir_conn->rend_data))) {
+ rend_client_refetch_v2_renddesc(dir_conn->rend_data);
+ }
+
+ /* Check for v3 rend desc fetch */
+ if (conn->purpose == DIR_PURPOSE_FETCH_HSDESC &&
+ dir_conn->hs_ident &&
+ !ed25519_public_key_is_zero(&dir_conn->hs_ident->identity_pk)) {
+ hs_client_refetch_hsdesc(&dir_conn->hs_ident->identity_pk);
+ }
+}
+
/** Called when we're about to finally unlink and free a directory connection:
* perform necessary accounting and cleanup */
void
@@ -3264,15 +3448,8 @@ connection_dir_about_to_close(dir_connection_t *dir_conn)
* failed: forget about this router, and maybe try again. */
connection_dir_request_failed(dir_conn);
}
- /* If we were trying to fetch a v2 rend desc and did not succeed,
- * retry as needed. (If a fetch is successful, the connection state
- * is changed to DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2 to mark that
- * refetching is unnecessary.) */
- if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 &&
- dir_conn->rend_data &&
- strlen(rend_data_get_address(dir_conn->rend_data)) ==
- REND_SERVICE_ID_LEN_BASE32)
- rend_client_refetch_v2_renddesc(dir_conn->rend_data);
+
+ refetch_hsdesc_if_needed(dir_conn);
}
/** Create an http response for the client <b>conn</b> out of
@@ -4918,7 +5095,7 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers,
goto done;
}
- if (authdir_mode_handles_descs(options, -1) &&
+ if (authdir_mode(options) &&
!strcmp(url,"/tor/")) { /* server descriptor post */
const char *msg = "[None]";
uint8_t purpose = authdir_mode_bridge(options) ?
@@ -5124,7 +5301,7 @@ connection_dir_finished_connecting(dir_connection_t *conn)
* Helper function for download_status_increment_failure(),
* download_status_reset(), and download_status_increment_attempt(). */
STATIC const smartlist_t *
-find_dl_schedule(download_status_t *dls, const or_options_t *options)
+find_dl_schedule(const download_status_t *dls, const or_options_t *options)
{
const int dir_server = dir_server_mode(options);
const int multi_d = networkstatus_consensus_can_use_multiple_directories(
@@ -5193,6 +5370,8 @@ find_dl_min_and_max_delay(download_status_t *dls, const or_options_t *options,
const smartlist_t *schedule = find_dl_schedule(dls, options);
tor_assert(schedule != NULL && smartlist_len(schedule) >= 2);
*min = *((int *)(smartlist_get(schedule, 0)));
+ /* Increment on failure schedules always use exponential backoff, but they
+ * have a smaller limit when they're deterministic */
if (dls->backoff == DL_SCHED_DETERMINISTIC)
*max = *((int *)((smartlist_get(schedule, smartlist_len(schedule) - 1))));
else
@@ -5201,8 +5380,9 @@ find_dl_min_and_max_delay(download_status_t *dls, const or_options_t *options,
/** Advance one delay step. The algorithm is to use the previous delay to
* compute an increment, we construct a value uniformly at random between
- * delay and MAX(delay*2,delay+1). We then clamp that value to be no larger
- * than max_delay, and return it.
+ * delay+1 and (delay*(DIR_DEFAULT_RANDOM_MULTIPLIER+1))+1 (or
+ * DIR_TEST_NET_RANDOM_MULTIPLIER in test networks).
+ * We then clamp that value to be no larger than max_delay, and return it.
*
* Requires that delay is less than INT_MAX, and delay is in [0,max_delay].
*/
@@ -5221,11 +5401,11 @@ next_random_exponential_delay(int delay, int max_delay)
/* How much are we willing to add to the delay? */
int max_increment;
- int multiplier = 3; /* no more than quadruple the previous delay */
+ int multiplier = DIR_DEFAULT_RANDOM_MULTIPLIER;
if (get_options()->TestingTorNetwork) {
/* Decrease the multiplier in testing networks. This reduces the variance,
* so that bootstrap is more reliable. */
- multiplier = 2; /* no more than triple the previous delay */
+ multiplier = DIR_TEST_NET_RANDOM_MULTIPLIER;
}
if (delay && delay < (INT_MAX-1) / multiplier) {
@@ -5377,6 +5557,11 @@ download_status_increment_failure(download_status_t *dls, int status_code,
tor_assert(dls);
+ /* dls wasn't reset before it was used */
+ if (dls->next_attempt_at == 0) {
+ download_status_reset(dls);
+ }
+
/* count the failure */
if (dls->n_download_failures < IMPOSSIBLE_TO_DOWNLOAD-1) {
++dls->n_download_failures;
@@ -5401,14 +5586,16 @@ download_status_increment_failure(download_status_t *dls, int status_code,
download_status_log_helper(item, !dls->increment_on, "failed",
"concurrently", dls->n_download_failures,
- increment, dls->next_attempt_at, now);
+ increment,
+ download_status_get_next_attempt_at(dls),
+ now);
if (dls->increment_on == DL_SCHED_INCREMENT_ATTEMPT) {
/* stop this schedule retrying on failure, it will launch concurrent
* connections instead */
return TIME_MAX;
} else {
- return dls->next_attempt_at;
+ return download_status_get_next_attempt_at(dls);
}
}
@@ -5429,6 +5616,11 @@ download_status_increment_attempt(download_status_t *dls, const char *item,
tor_assert(dls);
+ /* dls wasn't reset before it was used */
+ if (dls->next_attempt_at == 0) {
+ download_status_reset(dls);
+ }
+
if (dls->increment_on == DL_SCHED_INCREMENT_FAILURE) {
/* this schedule should retry on failure, and not launch any concurrent
attempts */
@@ -5447,9 +5639,19 @@ download_status_increment_attempt(download_status_t *dls, const char *item,
download_status_log_helper(item, dls->increment_on, "attempted",
"on failure", dls->n_download_attempts,
- delay, dls->next_attempt_at, now);
+ delay, download_status_get_next_attempt_at(dls),
+ now);
- return dls->next_attempt_at;
+ return download_status_get_next_attempt_at(dls);
+}
+
+static time_t
+download_status_get_initial_delay_from_now(const download_status_t *dls)
+{
+ const smartlist_t *schedule = find_dl_schedule(dls, get_options());
+ /* We use constant initial delays, even in exponential backoff
+ * schedules. */
+ return time(NULL) + *(int *)smartlist_get(schedule, 0);
}
/** Reset <b>dls</b> so that it will be considered downloadable
@@ -5470,11 +5672,9 @@ download_status_reset(download_status_t *dls)
|| dls->n_download_attempts == IMPOSSIBLE_TO_DOWNLOAD)
return; /* Don't reset this. */
- const smartlist_t *schedule = find_dl_schedule(dls, get_options());
-
dls->n_download_failures = 0;
dls->n_download_attempts = 0;
- dls->next_attempt_at = time(NULL) + *(int *)smartlist_get(schedule, 0);
+ dls->next_attempt_at = download_status_get_initial_delay_from_now(dls);
dls->last_backoff_position = 0;
dls->last_delay_used = 0;
/* Don't reset dls->want_authority or dls->increment_on */
@@ -5501,6 +5701,12 @@ download_status_get_n_attempts(const download_status_t *dls)
time_t
download_status_get_next_attempt_at(const download_status_t *dls)
{
+ /* dls wasn't reset before it was used */
+ if (dls->next_attempt_at == 0) {
+ /* so give the answer we would have given if it had been */
+ return download_status_get_initial_delay_from_now(dls);
+ }
+
return dls->next_attempt_at;
}
diff --git a/src/or/directory.h b/src/or/directory.h
index 14d5ae9ef4..42bcb55eb2 100644
--- a/src/or/directory.h
+++ b/src/or/directory.h
@@ -12,6 +12,8 @@
#ifndef TOR_DIRECTORY_H
#define TOR_DIRECTORY_H
+#include "hs_ident.h"
+
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,
@@ -71,6 +73,10 @@ void directory_request_set_if_modified_since(directory_request_t *req,
time_t if_modified_since);
void directory_request_set_rend_query(directory_request_t *req,
const rend_data_t *query);
+void directory_request_upload_set_hs_ident(directory_request_t *req,
+ const hs_ident_dir_conn_t *ident);
+void directory_request_fetch_set_hs_ident(directory_request_t *req,
+ const hs_ident_dir_conn_t *ident);
void directory_request_set_routerstatus(directory_request_t *req,
const routerstatus_t *rs);
@@ -123,12 +129,19 @@ time_t download_status_increment_attempt(download_status_t *dls,
void download_status_reset(download_status_t *dls);
static int download_status_is_ready(download_status_t *dls, time_t now,
int max_failures);
+time_t download_status_get_next_attempt_at(const download_status_t *dls);
+
/** Return true iff, as of <b>now</b>, the resource tracked by <b>dls</b> is
* ready to get its download reattempted. */
static inline int
download_status_is_ready(download_status_t *dls, time_t now,
int max_failures)
{
+ /* dls wasn't reset before it was used */
+ if (dls->next_attempt_at == 0) {
+ download_status_reset(dls);
+ }
+
if (dls->backoff == DL_SCHED_DETERMINISTIC) {
/* Deterministic schedules can hit an endpoint; exponential backoff
* schedules just wait longer and longer. */
@@ -137,7 +150,7 @@ download_status_is_ready(download_status_t *dls, time_t now,
if (!under_failure_limit)
return 0;
}
- return dls->next_attempt_at <= now;
+ return download_status_get_next_attempt_at(dls) <= now;
}
static void download_status_mark_impossible(download_status_t *dl);
@@ -151,13 +164,22 @@ download_status_mark_impossible(download_status_t *dl)
int download_status_get_n_failures(const download_status_t *dls);
int download_status_get_n_attempts(const download_status_t *dls);
-time_t download_status_get_next_attempt_at(const download_status_t *dls);
int purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose,
const char *resource);
#ifdef DIRECTORY_PRIVATE
+/** A structure to hold arguments passed into each directory response
+ * handler */
+typedef struct response_handler_args_t {
+ int status_code;
+ const char *reason;
+ const char *body;
+ size_t body_len;
+ const char *headers;
+} response_handler_args_t;
+
struct get_handler_args_t;
STATIC int handle_get_hs_descriptor_v3(dir_connection_t *conn,
const struct get_handler_args_t *args);
@@ -166,10 +188,13 @@ STATIC char *accept_encoding_header(void);
STATIC int allowed_anonymous_connection_compression_method(compress_method_t);
STATIC void warn_disallowed_anonymous_compression_method(compress_method_t);
+STATIC int handle_response_fetch_hsdesc_v3(dir_connection_t *conn,
+ const response_handler_args_t *args);
+
#endif
#ifdef TOR_UNIT_TESTS
-/* Used only by test_dir.c */
+/* Used only by test_dir.c and test_hs_cache.c */
STATIC int parse_http_url(const char *headers, char **url);
STATIC dirinfo_type_t dir_fetch_type(int dir_purpose, int router_purpose,
@@ -193,7 +218,7 @@ STATIC char* authdir_type_to_string(dirinfo_type_t auth);
STATIC const char * dir_conn_purpose_to_string(int purpose);
STATIC int should_use_directory_guards(const or_options_t *options);
STATIC compression_level_t choose_compression_level(ssize_t n_bytes);
-STATIC const smartlist_t *find_dl_schedule(download_status_t *dls,
+STATIC const smartlist_t *find_dl_schedule(const download_status_t *dls,
const or_options_t *options);
STATIC void find_dl_min_and_max_delay(download_status_t *dls,
const or_options_t *options,
@@ -206,5 +231,15 @@ STATIC int parse_hs_version_from_post(const char *url, const char *prefix,
STATIC unsigned parse_accept_encoding_header(const char *h);
#endif
+#if defined(TOR_UNIT_TESTS) || defined(DIRECTORY_PRIVATE)
+/* Used only by directory.c and test_dir.c */
+
+/* no more than quadruple the previous delay (multiplier + 1) */
+#define DIR_DEFAULT_RANDOM_MULTIPLIER (3)
+/* no more than triple the previous delay */
+#define DIR_TEST_NET_RANDOM_MULTIPLIER (2)
+
+#endif
+
#endif
diff --git a/src/or/dirserv.c b/src/or/dirserv.c
index acd00322a1..4faca9454b 100644
--- a/src/or/dirserv.c
+++ b/src/or/dirserv.c
@@ -704,10 +704,22 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source)
/* Do keypinning again ... this time, to add the pin if appropriate */
int keypin_status;
if (ri->cache_info.signing_key_cert) {
+ ed25519_public_key_t *pkey = &ri->cache_info.signing_key_cert->signing_key;
+ /* First let's validate this pubkey before pinning it */
+ if (ed25519_validate_pubkey(pkey) < 0) {
+ log_warn(LD_DIRSERV, "Received bad key from %s (source %s)",
+ router_describe(ri), source);
+ control_event_or_authdir_new_descriptor("REJECTED",
+ ri->cache_info.signed_descriptor_body,
+ desclen, *msg);
+ routerinfo_free(ri);
+ return ROUTER_AUTHDIR_REJECTS;
+ }
+
+ /* Now pin it! */
keypin_status = keypin_check_and_add(
(const uint8_t*)ri->cache_info.identity_digest,
- ri->cache_info.signing_key_cert->signing_key.pubkey,
- ! key_pinning);
+ pkey->pubkey, ! key_pinning);
} else {
keypin_status = keypin_check_lone_rsa(
(const uint8_t*)ri->cache_info.identity_digest);
@@ -2693,7 +2705,7 @@ measured_bw_line_parse(measured_bw_line_t *out, const char *orig_line)
}
cp+=strlen("bw=");
- out->bw_kb = tor_parse_long(cp, 0, 0, LONG_MAX, &parse_ok, &endptr);
+ out->bw_kb = tor_parse_long(cp, 10, 0, LONG_MAX, &parse_ok, &endptr);
if (!parse_ok || (*endptr && !TOR_ISSPACE(*endptr))) {
log_warn(LD_DIRSERV, "Invalid bandwidth in bandwidth file line: %s",
escaped(orig_line));
diff --git a/src/or/dirvote.c b/src/or/dirvote.c
index f5e29eb786..c65945fea7 100644
--- a/src/or/dirvote.c
+++ b/src/or/dirvote.c
@@ -306,7 +306,6 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key,
signing_key_fingerprint);
}
- note_crypto_pk_op(SIGN_DIR);
{
char *sig = router_get_dirobj_signature(digest, DIGEST_LEN,
private_signing_key);
@@ -737,12 +736,12 @@ dirvote_get_intermediate_param_value(const smartlist_t *param_list,
}
} SMARTLIST_FOREACH_END(k_v_pair);
- if (n_found == 1)
+ if (n_found == 1) {
return value;
- else if (BUG(n_found > 1))
- return default_val;
- else
+ } else {
+ tor_assert_nonfatal(n_found == 0);
return default_val;
+ }
}
/** Minimum number of directory authorities voting for a parameter to
diff --git a/src/or/dns.c b/src/or/dns.c
index 98b684c904..2d642773f3 100644
--- a/src/or/dns.c
+++ b/src/or/dns.c
@@ -182,6 +182,18 @@ evdns_log_cb(int warn, const char *msg)
} else if (!strcmp(msg, "All nameservers have failed")) {
control_event_server_status(LOG_WARN, "NAMESERVER_ALL_DOWN");
all_down = 1;
+ } else if (!strcmpstart(msg, "Address mismatch on received DNS")) {
+ static ratelim_t mismatch_limit = RATELIM_INIT(3600);
+ const char *src = strstr(msg, " Apparent source");
+ if (!src || get_options()->SafeLogging) {
+ src = "";
+ }
+ log_fn_ratelim(&mismatch_limit, severity, LD_EXIT,
+ "eventdns: Received a DNS packet from "
+ "an IP address to which we did not send a request. This "
+ "could be a DNS spoofing attempt, or some kind of "
+ "misconfiguration.%s", src);
+ return;
}
tor_log(severity, LD_EXIT, "eventdns: %s", msg);
}
@@ -366,7 +378,7 @@ set_expiry(cached_resolve_t *resolve, time_t expires)
resolve->expire = expires;
smartlist_pqueue_add(cached_resolve_pqueue,
compare_cached_resolves_by_expiry_,
- STRUCT_OFFSET(cached_resolve_t, minheap_idx),
+ offsetof(cached_resolve_t, minheap_idx),
resolve);
}
@@ -413,7 +425,7 @@ purge_expired_resolves(time_t now)
break;
smartlist_pqueue_pop(cached_resolve_pqueue,
compare_cached_resolves_by_expiry_,
- STRUCT_OFFSET(cached_resolve_t, minheap_idx));
+ offsetof(cached_resolve_t, minheap_idx));
if (resolve->state == CACHE_STATE_PENDING) {
log_debug(LD_EXIT,
@@ -1928,7 +1940,7 @@ dns_launch_wildcard_checks(void)
launch_wildcard_check(8, 16, ipv6, ".com");
launch_wildcard_check(8, 16, ipv6, ".org");
launch_wildcard_check(8, 16, ipv6, ".net");
- }
+ }
}
}
@@ -2071,7 +2083,7 @@ assert_cache_ok_(void)
smartlist_pqueue_assert_ok(cached_resolve_pqueue,
compare_cached_resolves_by_expiry_,
- STRUCT_OFFSET(cached_resolve_t, minheap_idx));
+ offsetof(cached_resolve_t, minheap_idx));
SMARTLIST_FOREACH(cached_resolve_pqueue, cached_resolve_t *, res,
{
diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c
index fa768fc4a6..739ec82484 100644
--- a/src/or/entrynodes.c
+++ b/src/or/entrynodes.c
@@ -813,7 +813,7 @@ STATIC entry_guard_t *
entry_guard_add_to_sample(guard_selection_t *gs,
const node_t *node)
{
- log_info(LD_GUARD, "Adding %s as to the entry guard sample set.",
+ log_info(LD_GUARD, "Adding %s to the entry guard sample set.",
node_describe(node));
/* make sure that the guard is not already sampled. */
diff --git a/src/or/ext_orport.c b/src/or/ext_orport.c
index b60d2e55c8..01dc06ce13 100644
--- a/src/or/ext_orport.c
+++ b/src/or/ext_orport.c
@@ -31,7 +31,7 @@
ext_or_cmd_t *
ext_or_cmd_new(uint16_t len)
{
- size_t size = STRUCT_OFFSET(ext_or_cmd_t, body) + len;
+ size_t size = offsetof(ext_or_cmd_t, body) + len;
ext_or_cmd_t *cmd = tor_malloc(size);
cmd->len = len;
return cmd;
diff --git a/src/or/hibernate.c b/src/or/hibernate.c
index f54109fc90..8c48a6f47d 100644
--- a/src/or/hibernate.c
+++ b/src/or/hibernate.c
@@ -587,7 +587,10 @@ accounting_set_wakeup_time(void)
char buf[ISO_TIME_LEN+1];
format_iso_time(buf, interval_start_time);
- crypto_pk_get_digest(get_server_identity_key(), digest);
+ if (crypto_pk_get_digest(get_server_identity_key(), digest) < 0) {
+ log_err(LD_BUG, "Error getting our key's digest.");
+ tor_assert(0);
+ }
d_env = crypto_digest_new();
crypto_digest_add_bytes(d_env, buf, ISO_TIME_LEN);
diff --git a/src/or/hs_cache.c b/src/or/hs_cache.c
index 29681b42b5..6962c5ce44 100644
--- a/src/or/hs_cache.c
+++ b/src/or/hs_cache.c
@@ -9,15 +9,19 @@
/* For unit tests.*/
#define HS_CACHE_PRIVATE
-#include "hs_cache.h"
-
#include "or.h"
#include "config.h"
+#include "hs_ident.h"
#include "hs_common.h"
+#include "hs_client.h"
#include "hs_descriptor.h"
#include "networkstatus.h"
#include "rendcache.h"
+#include "hs_cache.h"
+
+/********************** Directory HS cache ******************/
+
/* Directory descriptor cache. Map indexed by blinded key. */
static digest256map_t *hs_cache_v3_dir;
@@ -98,7 +102,7 @@ cache_dir_desc_new(const char *desc)
/* Return the size of a cache entry in bytes. */
static size_t
-cache_get_entry_size(const hs_cache_dir_descriptor_t *entry)
+cache_get_dir_entry_size(const hs_cache_dir_descriptor_t *entry)
{
return (sizeof(*entry) + hs_desc_plaintext_obj_size(entry->plaintext_data)
+ strlen(entry->encoded_desc));
@@ -124,15 +128,17 @@ cache_store_v3_as_dir(hs_cache_dir_descriptor_t *desc)
if (cache_entry->plaintext_data->revision_counter >=
desc->plaintext_data->revision_counter) {
log_info(LD_REND, "Descriptor revision counter in our cache is "
- "greater or equal than the one we received. "
- "Rejecting!");
+ "greater or equal than the one we received (%d/%d). "
+ "Rejecting!",
+ (int)cache_entry->plaintext_data->revision_counter,
+ (int)desc->plaintext_data->revision_counter);
goto err;
}
/* We now know that the descriptor we just received is a new one so
* remove the entry we currently have from our cache so we can then
* store the new one. */
remove_v3_desc_as_dir(cache_entry);
- rend_cache_decrement_allocation(cache_get_entry_size(cache_entry));
+ rend_cache_decrement_allocation(cache_get_dir_entry_size(cache_entry));
cache_dir_desc_free(cache_entry);
}
/* Store the descriptor we just got. We are sure here that either we
@@ -142,7 +148,7 @@ cache_store_v3_as_dir(hs_cache_dir_descriptor_t *desc)
/* Update our total cache size with this entry for the OOM. This uses the
* old HS protocol cache subsystem for which we are tied with. */
- rend_cache_increment_allocation(cache_get_entry_size(desc));
+ rend_cache_increment_allocation(cache_get_dir_entry_size(desc));
/* XXX: Update HS statistics. We should have specific stats for v3. */
@@ -219,7 +225,7 @@ cache_clean_v3_as_dir(time_t now, time_t global_cutoff)
}
/* Here, our entry has expired, remove and free. */
MAP_DEL_CURRENT(key);
- entry_size = cache_get_entry_size(entry);
+ entry_size = cache_get_dir_entry_size(entry);
bytes_removed += entry_size;
/* Entry is not in the cache anymore, destroy it. */
cache_dir_desc_free(entry);
@@ -313,6 +319,468 @@ hs_cache_clean_as_dir(time_t now)
cache_clean_v3_as_dir(now, 0);
}
+/********************** Client-side HS cache ******************/
+
+/* Client-side HS descriptor cache. Map indexed by service identity key. */
+static digest256map_t *hs_cache_v3_client;
+
+/* Client-side introduction point state cache. Map indexed by service public
+ * identity key (onion address). It contains hs_cache_client_intro_state_t
+ * objects all related to a specific service. */
+static digest256map_t *hs_cache_client_intro_state;
+
+/* Return the size of a client cache entry in bytes. */
+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);
+}
+
+/* Remove a given descriptor from our cache. */
+static void
+remove_v3_desc_as_client(const hs_cache_client_descriptor_t *desc)
+{
+ tor_assert(desc);
+ digest256map_remove(hs_cache_v3_client, desc->key.pubkey);
+ /* Update cache size with this entry for the OOM handler. */
+ rend_cache_decrement_allocation(cache_get_client_entry_size(desc));
+}
+
+/* Store a given descriptor in our cache. */
+static void
+store_v3_desc_as_client(hs_cache_client_descriptor_t *desc)
+{
+ tor_assert(desc);
+ digest256map_set(hs_cache_v3_client, desc->key.pubkey, desc);
+ /* Update cache size with this entry for the OOM handler. */
+ rend_cache_increment_allocation(cache_get_client_entry_size(desc));
+}
+
+/* Query our cache and return the entry or NULL if not found. */
+STATIC hs_cache_client_descriptor_t *
+lookup_v3_desc_as_client(const uint8_t *key)
+{
+ tor_assert(key);
+ return digest256map_get(hs_cache_v3_client, key);
+}
+
+/* Parse the encoded descriptor in <b>desc_str</b> using
+ * <b>service_identity_pk<b> to decrypt it first.
+ *
+ * If everything goes well, allocate and return a new
+ * hs_cache_client_descriptor_t object. In case of error, return NULL. */
+static hs_cache_client_descriptor_t *
+cache_client_desc_new(const char *desc_str,
+ const ed25519_public_key_t *service_identity_pk)
+{
+ hs_descriptor_t *desc = NULL;
+ hs_cache_client_descriptor_t *client_desc = NULL;
+
+ tor_assert(desc_str);
+ tor_assert(service_identity_pk);
+
+ /* Decode the descriptor we just fetched. */
+ if (hs_client_decode_descriptor(desc_str, service_identity_pk, &desc) < 0) {
+ goto end;
+ }
+ tor_assert(desc);
+
+ /* All is good: make a cache object for this descriptor */
+ client_desc = tor_malloc_zero(sizeof(hs_cache_client_descriptor_t));
+ ed25519_pubkey_copy(&client_desc->key, service_identity_pk);
+ client_desc->created_ts = approx_time();
+ client_desc->desc = desc;
+ client_desc->encoded_desc = tor_strdup(desc_str);
+
+ end:
+ return client_desc;
+}
+
+/** Free memory allocated by <b>desc</b>. */
+static void
+cache_client_desc_free(hs_cache_client_descriptor_t *desc)
+{
+ if (desc == NULL) {
+ return;
+ }
+ hs_descriptor_free(desc->desc);
+ memwipe(&desc->key, 0, sizeof(desc->key));
+ memwipe(desc->encoded_desc, 0, strlen(desc->encoded_desc));
+ tor_free(desc->encoded_desc);
+ tor_free(desc);
+}
+
+/** Helper function: Use by the free all function to clear the client cache */
+static void
+cache_client_desc_free_(void *ptr)
+{
+ hs_cache_client_descriptor_t *desc = ptr;
+ cache_client_desc_free(desc);
+}
+
+/* Return a newly allocated and initialized hs_cache_intro_state_t object. */
+static hs_cache_intro_state_t *
+cache_intro_state_new(void)
+{
+ hs_cache_intro_state_t *state = tor_malloc_zero(sizeof(*state));
+ state->created_ts = approx_time();
+ return state;
+}
+
+/* Free an hs_cache_intro_state_t object. */
+static void
+cache_intro_state_free(hs_cache_intro_state_t *state)
+{
+ tor_free(state);
+}
+
+/* Helper function: use by the free all function. */
+static void
+cache_intro_state_free_(void *state)
+{
+ cache_intro_state_free(state);
+}
+
+/* Return a newly allocated and initialized hs_cache_client_intro_state_t
+ * object. */
+static hs_cache_client_intro_state_t *
+cache_client_intro_state_new(void)
+{
+ hs_cache_client_intro_state_t *cache = tor_malloc_zero(sizeof(*cache));
+ cache->intro_points = digest256map_new();
+ return cache;
+}
+
+/* Free a cache client intro state object. */
+static void
+cache_client_intro_state_free(hs_cache_client_intro_state_t *cache)
+{
+ if (cache == NULL) {
+ return;
+ }
+ digest256map_free(cache->intro_points, cache_intro_state_free_);
+ tor_free(cache);
+}
+
+/* Helper function: use by the free all function. */
+static void
+cache_client_intro_state_free_(void *entry)
+{
+ cache_client_intro_state_free(entry);
+}
+
+/* For the given service identity key service_pk and an introduction
+ * authentication key auth_key, lookup the intro state object. Return 1 if
+ * found and put it in entry if not NULL. Return 0 if not found and entry is
+ * untouched. */
+static int
+cache_client_intro_state_lookup(const ed25519_public_key_t *service_pk,
+ const ed25519_public_key_t *auth_key,
+ hs_cache_intro_state_t **entry)
+{
+ hs_cache_intro_state_t *state;
+ hs_cache_client_intro_state_t *cache;
+
+ tor_assert(service_pk);
+ tor_assert(auth_key);
+
+ /* Lookup the intro state cache for this service key. */
+ cache = digest256map_get(hs_cache_client_intro_state, service_pk->pubkey);
+ if (cache == NULL) {
+ goto not_found;
+ }
+
+ /* From the cache we just found for the service, lookup in the introduction
+ * points map for the given authentication key. */
+ state = digest256map_get(cache->intro_points, auth_key->pubkey);
+ if (state == NULL) {
+ goto not_found;
+ }
+ if (entry) {
+ *entry = state;
+ }
+ return 1;
+ not_found:
+ return 0;
+}
+
+/* Note the given failure in state. */
+static void
+cache_client_intro_state_note(hs_cache_intro_state_t *state,
+ rend_intro_point_failure_t failure)
+{
+ tor_assert(state);
+ switch (failure) {
+ case INTRO_POINT_FAILURE_GENERIC:
+ state->error = 1;
+ break;
+ case INTRO_POINT_FAILURE_TIMEOUT:
+ state->timed_out = 1;
+ break;
+ case INTRO_POINT_FAILURE_UNREACHABLE:
+ state->unreachable_count++;
+ break;
+ default:
+ tor_assert_nonfatal_unreached();
+ return;
+ }
+}
+
+/* For the given service identity key service_pk and an introduction
+ * authentication key auth_key, add an entry in the client intro state cache
+ * If no entry exists for the service, it will create one. If state is non
+ * NULL, it will point to the new intro state entry. */
+static void
+cache_client_intro_state_add(const ed25519_public_key_t *service_pk,
+ const ed25519_public_key_t *auth_key,
+ hs_cache_intro_state_t **state)
+{
+ hs_cache_intro_state_t *entry, *old_entry;
+ hs_cache_client_intro_state_t *cache;
+
+ tor_assert(service_pk);
+ tor_assert(auth_key);
+
+ /* Lookup the state cache for this service key. */
+ cache = digest256map_get(hs_cache_client_intro_state, service_pk->pubkey);
+ if (cache == NULL) {
+ cache = cache_client_intro_state_new();
+ digest256map_set(hs_cache_client_intro_state, service_pk->pubkey, cache);
+ }
+
+ entry = cache_intro_state_new();
+ old_entry = digest256map_set(cache->intro_points, auth_key->pubkey, entry);
+ /* This should never happened because the code flow is to lookup the entry
+ * before adding it. But, just in case, non fatal assert and free it. */
+ tor_assert_nonfatal(old_entry == NULL);
+ tor_free(old_entry);
+
+ if (state) {
+ *state = entry;
+ }
+}
+
+/* Remove every intro point state entry from cache that has been created
+ * before or at the cutoff. */
+static void
+cache_client_intro_state_clean(time_t cutoff,
+ hs_cache_client_intro_state_t *cache)
+{
+ tor_assert(cache);
+
+ DIGEST256MAP_FOREACH_MODIFY(cache->intro_points, key,
+ hs_cache_intro_state_t *, entry) {
+ if (entry->created_ts <= cutoff) {
+ cache_intro_state_free(entry);
+ MAP_DEL_CURRENT(key);
+ }
+ } DIGEST256MAP_FOREACH_END;
+}
+
+/* Return true iff no intro points are in this cache. */
+static int
+cache_client_intro_state_is_empty(const hs_cache_client_intro_state_t *cache)
+{
+ return digest256map_isempty(cache->intro_points);
+}
+
+/** Check whether <b>client_desc</b> is useful for us, and store it in the
+ * client-side HS cache if so. The client_desc is freed if we already have a
+ * fresher (higher revision counter count) in the cache. */
+static int
+cache_store_as_client(hs_cache_client_descriptor_t *client_desc)
+{
+ hs_cache_client_descriptor_t *cache_entry;
+
+ /* TODO: Heavy code duplication with cache_store_as_dir(). Consider
+ * refactoring and uniting! */
+
+ tor_assert(client_desc);
+
+ /* Check if we already have a descriptor from this HS in cache. If we do,
+ * check if this descriptor is newer than the cached one */
+ cache_entry = lookup_v3_desc_as_client(client_desc->key.pubkey);
+ if (cache_entry != NULL) {
+ /* 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 >
+ client_desc->desc->plaintext_data.revision_counter) {
+ log_info(LD_REND, "We already have fresher descriptor. Ignoring.");
+ cache_client_desc_free(client_desc);
+ goto done;
+ }
+ /* Remove old entry. Make space for the new one! */
+ remove_v3_desc_as_client(cache_entry);
+ cache_client_desc_free(cache_entry);
+ }
+
+ /* Store descriptor in cache */
+ store_v3_desc_as_client(client_desc);
+
+ done:
+ return 0;
+}
+
+/* Clean the client cache using now as the current time. Return the total size
+ * of removed bytes from the cache. */
+static size_t
+cache_clean_v3_as_client(time_t now)
+{
+ size_t bytes_removed = 0;
+
+ if (!hs_cache_v3_client) { /* No cache to clean. Just return. */
+ return 0;
+ }
+
+ DIGEST256MAP_FOREACH_MODIFY(hs_cache_v3_client, key,
+ hs_cache_client_descriptor_t *, entry) {
+ size_t entry_size;
+ time_t cutoff = now - rend_cache_max_entry_lifetime();
+
+ /* If the entry has been created _after_ the cutoff, not expired so
+ * continue to the next entry in our v3 cache. */
+ if (entry->created_ts > cutoff) {
+ continue;
+ }
+ /* Here, our entry has expired, remove and free. */
+ MAP_DEL_CURRENT(key);
+ entry_size = cache_get_client_entry_size(entry);
+ bytes_removed += entry_size;
+ /* 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
+ * a loop so we have to explicitely decrement. */
+ rend_cache_decrement_allocation(entry_size);
+ /* Logging. */
+ {
+ char key_b64[BASE64_DIGEST256_LEN + 1];
+ base64_encode(key_b64, sizeof(key_b64), (const char *) key,
+ DIGEST256_LEN, 0);
+ log_info(LD_REND, "Removing hidden service v3 descriptor '%s' "
+ "from client cache",
+ safe_str_client(key_b64));
+ }
+ } DIGEST256MAP_FOREACH_END;
+
+ return bytes_removed;
+}
+
+/** Public API: Given the HS ed25519 identity public key in <b>key</b>, return
+ * its HS descriptor if it's stored in our cache, or NULL if not. */
+const hs_descriptor_t *
+hs_cache_lookup_as_client(const ed25519_public_key_t *key)
+{
+ hs_cache_client_descriptor_t *cached_desc = NULL;
+
+ tor_assert(key);
+
+ cached_desc = lookup_v3_desc_as_client(key->pubkey);
+ if (cached_desc) {
+ tor_assert(cached_desc->desc);
+ return cached_desc->desc;
+ }
+
+ return NULL;
+}
+
+/** Public API: Given an encoded descriptor, store it in the client HS
+ * cache. Return -1 on error, 0 on success .*/
+int
+hs_cache_store_as_client(const char *desc_str,
+ const ed25519_public_key_t *identity_pk)
+{
+ hs_cache_client_descriptor_t *client_desc = NULL;
+
+ tor_assert(desc_str);
+ tor_assert(identity_pk);
+
+ /* Create client cache descriptor object */
+ client_desc = cache_client_desc_new(desc_str, identity_pk);
+ if (!client_desc) {
+ log_warn(LD_GENERAL, "Failed to parse received descriptor %s.",
+ escaped(desc_str));
+ goto err;
+ }
+
+ /* Push it to the cache */
+ if (cache_store_as_client(client_desc) < 0) {
+ goto err;
+ }
+
+ return 0;
+
+ err:
+ cache_client_desc_free(client_desc);
+ return -1;
+}
+
+/* Clean all client caches using the current time now. */
+void
+hs_cache_clean_as_client(time_t now)
+{
+ /* Start with v2 cache cleaning. */
+ rend_cache_clean(now, REND_CACHE_TYPE_CLIENT);
+ /* Now, clean the v3 cache. Set the cutoff to 0 telling the cleanup function
+ * to compute the cutoff by itself using the lifetime value. */
+ cache_clean_v3_as_client(now);
+}
+
+/* For a given service identity public key and an introduction authentication
+ * key, note the given failure in the client intro state cache. */
+void
+hs_cache_client_intro_state_note(const ed25519_public_key_t *service_pk,
+ const ed25519_public_key_t *auth_key,
+ rend_intro_point_failure_t failure)
+{
+ int found;
+ hs_cache_intro_state_t *entry;
+
+ tor_assert(service_pk);
+ tor_assert(auth_key);
+
+ found = cache_client_intro_state_lookup(service_pk, auth_key, &entry);
+ if (!found) {
+ /* Create a new entry and add it to the cache. */
+ cache_client_intro_state_add(service_pk, auth_key, &entry);
+ }
+ /* Note down the entry. */
+ cache_client_intro_state_note(entry, failure);
+}
+
+/* For a given service identity public key and an introduction authentication
+ * key, return true iff it is present in the failure cache. */
+const hs_cache_intro_state_t *
+hs_cache_client_intro_state_find(const ed25519_public_key_t *service_pk,
+ const ed25519_public_key_t *auth_key)
+{
+ hs_cache_intro_state_t *state = NULL;
+ cache_client_intro_state_lookup(service_pk, auth_key, &state);
+ return state;
+}
+
+/* Cleanup the client introduction state cache. */
+void
+hs_cache_client_intro_state_clean(time_t now)
+{
+ time_t cutoff = now - HS_CACHE_CLIENT_INTRO_STATE_MAX_AGE;
+
+ DIGEST256MAP_FOREACH_MODIFY(hs_cache_client_intro_state, key,
+ hs_cache_client_intro_state_t *, cache) {
+ /* Cleanup intro points failure. */
+ cache_client_intro_state_clean(cutoff, cache);
+
+ /* Is this cache empty for this service key? If yes, remove it from the
+ * cache. Else keep it. */
+ if (cache_client_intro_state_is_empty(cache)) {
+ cache_client_intro_state_free(cache);
+ MAP_DEL_CURRENT(key);
+ }
+ } DIGEST256MAP_FOREACH_END;
+}
+
+/**************** Generics *********************************/
+
/* Do a round of OOM cleanup on all directory caches. Return the amount of
* removed bytes. It is possible that the returned value is lower than
* min_remove_bytes if the caches get emptied out so the caller should be
@@ -367,10 +835,7 @@ hs_cache_handle_oom(time_t now, size_t min_remove_bytes)
return bytes_removed;
}
-/**
- * Return the maximum size of an HS descriptor we are willing to accept as an
- * HSDir.
- */
+/* Return the maximum size of a v3 HS descriptor. */
unsigned int
hs_cache_get_max_descriptor_size(void)
{
@@ -386,6 +851,12 @@ hs_cache_init(void)
/* Calling this twice is very wrong code flow. */
tor_assert(!hs_cache_v3_dir);
hs_cache_v3_dir = digest256map_new();
+
+ tor_assert(!hs_cache_v3_client);
+ hs_cache_v3_client = digest256map_new();
+
+ tor_assert(!hs_cache_client_intro_state);
+ hs_cache_client_intro_state = digest256map_new();
}
/* Cleanup the hidden service cache subsystem. */
@@ -394,5 +865,12 @@ hs_cache_free_all(void)
{
digest256map_free(hs_cache_v3_dir, cache_dir_desc_free_);
hs_cache_v3_dir = NULL;
+
+ digest256map_free(hs_cache_v3_client, cache_client_desc_free_);
+ hs_cache_v3_client = NULL;
+
+ digest256map_free(hs_cache_client_intro_state,
+ cache_client_intro_state_free_);
+ hs_cache_client_intro_state = NULL;
}
diff --git a/src/or/hs_cache.h b/src/or/hs_cache.h
index ed00424234..2a4d2dbb2f 100644
--- a/src/or/hs_cache.h
+++ b/src/or/hs_cache.h
@@ -15,8 +15,34 @@
#include "crypto_ed25519.h"
#include "hs_common.h"
#include "hs_descriptor.h"
+#include "rendcommon.h"
#include "torcert.h"
+/* This is the maximum time an introduction point state object can stay in the
+ * client cache in seconds (2 mins or 120 seconds). */
+#define HS_CACHE_CLIENT_INTRO_STATE_MAX_AGE (2 * 60)
+
+/* Introduction point state. */
+typedef struct hs_cache_intro_state_t {
+ /* When this entry was created and put in the cache. */
+ time_t created_ts;
+
+ /* Did it suffered a generic error? */
+ unsigned int error : 1;
+
+ /* Did it timed out? */
+ unsigned int timed_out : 1;
+
+ /* How many times we tried to reached it and it was unreachable. */
+ uint32_t unreachable_count;
+} hs_cache_intro_state_t;
+
+typedef struct hs_cache_client_intro_state_t {
+ /* Contains hs_cache_intro_state_t object indexed by introduction point
+ * authentication key. */
+ digest256map_t *intro_points;
+} hs_cache_client_intro_state_t;
+
/* Descriptor representation on the directory side which is a subset of
* information that the HSDir can decode and serve it. */
typedef struct hs_cache_dir_descriptor_t {
@@ -53,10 +79,44 @@ int hs_cache_store_as_dir(const char *desc);
int hs_cache_lookup_as_dir(uint32_t version, const char *query,
const char **desc_out);
+const hs_descriptor_t *
+hs_cache_lookup_as_client(const ed25519_public_key_t *key);
+int hs_cache_store_as_client(const char *desc_str,
+ const ed25519_public_key_t *identity_pk);
+void hs_cache_clean_as_client(time_t now);
+
+/* Client failure cache. */
+void hs_cache_client_intro_state_note(const ed25519_public_key_t *service_pk,
+ const ed25519_public_key_t *auth_key,
+ rend_intro_point_failure_t failure);
+const hs_cache_intro_state_t *hs_cache_client_intro_state_find(
+ const ed25519_public_key_t *service_pk,
+ const ed25519_public_key_t *auth_key);
+void hs_cache_client_intro_state_clean(time_t now);
+
#ifdef HS_CACHE_PRIVATE
+/** Represents a locally cached HS descriptor on a hidden service client. */
+typedef struct hs_cache_client_descriptor_t {
+ /* This object is indexed using the service identity public key */
+ ed25519_public_key_t key;
+
+ /* When was this entry created. Used to expire entries. */
+ time_t created_ts;
+
+ /* The cached descriptor, this object is the owner. It can't be NULL. A
+ * cache object without a valid descriptor is not possible. */
+ hs_descriptor_t *desc;
+
+ /* Encoded descriptor in string form. Can't be NULL. */
+ char *encoded_desc;
+} hs_cache_client_descriptor_t;
+
STATIC size_t cache_clean_v3_as_dir(time_t now, time_t global_cutoff);
+STATIC hs_cache_client_descriptor_t *
+lookup_v3_desc_as_client(const uint8_t *key);
+
#endif /* HS_CACHE_PRIVATE */
#endif /* TOR_HS_CACHE_H */
diff --git a/src/or/hs_cell.c b/src/or/hs_cell.c
new file mode 100644
index 0000000000..5244cfa3dd
--- /dev/null
+++ b/src/or/hs_cell.c
@@ -0,0 +1,948 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_cell.c
+ * \brief Hidden service API for cell creation and handling.
+ **/
+
+#include "or.h"
+#include "config.h"
+#include "rendservice.h"
+#include "replaycache.h"
+#include "util.h"
+
+#include "hs_cell.h"
+#include "hs_ntor.h"
+
+/* Trunnel. */
+#include "ed25519_cert.h"
+#include "hs/cell_common.h"
+#include "hs/cell_establish_intro.h"
+#include "hs/cell_introduce1.h"
+#include "hs/cell_rendezvous.h"
+
+/* Compute the MAC of an INTRODUCE cell in mac_out. The encoded_cell param is
+ * the cell content up to the ENCRYPTED section of length encoded_cell_len.
+ * The encrypted param is the start of the ENCRYPTED section of length
+ * encrypted_len. The mac_key is the key needed for the computation of the MAC
+ * derived from the ntor handshake of length mac_key_len.
+ *
+ * The length mac_out_len must be at least DIGEST256_LEN. */
+static void
+compute_introduce_mac(const uint8_t *encoded_cell, size_t encoded_cell_len,
+ const uint8_t *encrypted, size_t encrypted_len,
+ const uint8_t *mac_key, size_t mac_key_len,
+ uint8_t *mac_out, size_t mac_out_len)
+{
+ size_t offset = 0;
+ size_t mac_msg_len;
+ uint8_t mac_msg[RELAY_PAYLOAD_SIZE] = {0};
+
+ tor_assert(encoded_cell);
+ tor_assert(encrypted);
+ tor_assert(mac_key);
+ tor_assert(mac_out);
+ tor_assert(mac_out_len >= DIGEST256_LEN);
+
+ /* Compute the size of the message which is basically the entire cell until
+ * the MAC field of course. */
+ mac_msg_len = encoded_cell_len + (encrypted_len - DIGEST256_LEN);
+ tor_assert(mac_msg_len <= sizeof(mac_msg));
+
+ /* First, put the encoded cell in the msg. */
+ memcpy(mac_msg, encoded_cell, encoded_cell_len);
+ offset += encoded_cell_len;
+ /* Second, put the CLIENT_PK + ENCRYPTED_DATA but ommit the MAC field (which
+ * is junk at this point). */
+ memcpy(mac_msg + offset, encrypted, (encrypted_len - DIGEST256_LEN));
+ offset += (encrypted_len - DIGEST256_LEN);
+ tor_assert(offset == mac_msg_len);
+
+ crypto_mac_sha3_256(mac_out, mac_out_len,
+ mac_key, mac_key_len,
+ mac_msg, mac_msg_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. */
+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,
+ const uint8_t *encrypted_section,
+ curve25519_public_key_t *client_pk)
+{
+ hs_ntor_intro_cell_keys_t *keys;
+
+ tor_assert(auth_key);
+ tor_assert(enc_key);
+ tor_assert(subcredential);
+ tor_assert(encrypted_section);
+ tor_assert(client_pk);
+
+ keys = tor_malloc_zero(sizeof(*keys));
+
+ /* 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) {
+ /* Don't rely on the caller to wipe this on error. */
+ memwipe(client_pk, 0, sizeof(curve25519_public_key_t));
+ tor_free(keys);
+ keys = NULL;
+ }
+ return keys;
+}
+
+/* Using the given encryption key, decrypt the encrypted_section of length
+ * encrypted_section_len of an INTRODUCE2 cell and return a newly allocated
+ * buffer containing the decrypted data. On decryption failure, NULL is
+ * returned. */
+static uint8_t *
+decrypt_introduce2(const uint8_t *enc_key, const uint8_t *encrypted_section,
+ size_t encrypted_section_len)
+{
+ uint8_t *decrypted = NULL;
+ crypto_cipher_t *cipher = NULL;
+
+ tor_assert(enc_key);
+ tor_assert(encrypted_section);
+
+ /* Decrypt ENCRYPTED section. */
+ cipher = crypto_cipher_new_with_bits((char *) enc_key,
+ CURVE25519_PUBKEY_LEN * 8);
+ tor_assert(cipher);
+
+ /* This is symmetric encryption so can't be bigger than the encrypted
+ * section length. */
+ decrypted = tor_malloc_zero(encrypted_section_len);
+ if (crypto_cipher_decrypt(cipher, (char *) decrypted,
+ (const char *) encrypted_section,
+ encrypted_section_len) < 0) {
+ tor_free(decrypted);
+ decrypted = NULL;
+ goto done;
+ }
+
+ done:
+ crypto_cipher_free(cipher);
+ return decrypted;
+}
+
+/* Given a pointer to the decrypted data of the ENCRYPTED section of an
+ * INTRODUCE2 cell of length decrypted_len, parse and validate the cell
+ * content. Return a newly allocated cell structure or NULL on error. The
+ * circuit and service object are only used for logging purposes. */
+static trn_cell_introduce_encrypted_t *
+parse_introduce2_encrypted(const uint8_t *decrypted_data,
+ size_t decrypted_len, const origin_circuit_t *circ,
+ const hs_service_t *service)
+{
+ trn_cell_introduce_encrypted_t *enc_cell = NULL;
+
+ tor_assert(decrypted_data);
+ tor_assert(circ);
+ tor_assert(service);
+
+ if (trn_cell_introduce_encrypted_parse(&enc_cell, decrypted_data,
+ decrypted_len) < 0) {
+ log_info(LD_REND, "Unable to parse the decrypted ENCRYPTED section of "
+ "the INTRODUCE2 cell on circuit %u for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto err;
+ }
+
+ if (trn_cell_introduce_encrypted_get_onion_key_type(enc_cell) !=
+ HS_CELL_ONION_KEY_TYPE_NTOR) {
+ log_info(LD_REND, "INTRODUCE2 onion key type is invalid. Got %u but "
+ "expected %u on circuit %u for service %s",
+ trn_cell_introduce_encrypted_get_onion_key_type(enc_cell),
+ HS_CELL_ONION_KEY_TYPE_NTOR, TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto err;
+ }
+
+ if (trn_cell_introduce_encrypted_getlen_onion_key(enc_cell) !=
+ CURVE25519_PUBKEY_LEN) {
+ log_info(LD_REND, "INTRODUCE2 onion key length is invalid. Got %u but "
+ "expected %d on circuit %u for service %s",
+ (unsigned)trn_cell_introduce_encrypted_getlen_onion_key(enc_cell),
+ CURVE25519_PUBKEY_LEN, TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto err;
+ }
+ /* XXX: Validate NSPEC field as well. */
+
+ return enc_cell;
+ err:
+ trn_cell_introduce_encrypted_free(enc_cell);
+ return NULL;
+}
+
+/* Build a legacy ESTABLISH_INTRO cell with the given circuit nonce and RSA
+ * encryption key. The encoded cell is put in cell_out that MUST at least be
+ * of the size of RELAY_PAYLOAD_SIZE. Return the encoded cell length on
+ * success else a negative value and cell_out is untouched. */
+static ssize_t
+build_legacy_establish_intro(const char *circ_nonce, crypto_pk_t *enc_key,
+ uint8_t *cell_out)
+{
+ ssize_t cell_len;
+
+ tor_assert(circ_nonce);
+ tor_assert(enc_key);
+ tor_assert(cell_out);
+
+ memwipe(cell_out, 0, RELAY_PAYLOAD_SIZE);
+
+ cell_len = rend_service_encode_establish_intro_cell((char*)cell_out,
+ RELAY_PAYLOAD_SIZE,
+ enc_key, circ_nonce);
+ return cell_len;
+}
+
+/* Parse an INTRODUCE2 cell from payload of size payload_len for the given
+ * service and circuit which are used only for logging purposes. The resulting
+ * parsed cell is put in cell_ptr_out.
+ *
+ * This function only parses prop224 INTRODUCE2 cells even when the intro point
+ * is a legacy intro point. That's because intro points don't actually care
+ * about the contents of the introduce cell. Legacy INTRODUCE cells are only
+ * used by the legacy system now.
+ *
+ * Return 0 on success else a negative value and cell_ptr_out is untouched. */
+static int
+parse_introduce2_cell(const hs_service_t *service,
+ const origin_circuit_t *circ, const uint8_t *payload,
+ size_t payload_len,
+ trn_cell_introduce1_t **cell_ptr_out)
+{
+ trn_cell_introduce1_t *cell = NULL;
+
+ tor_assert(service);
+ tor_assert(circ);
+ tor_assert(payload);
+ tor_assert(cell_ptr_out);
+
+ /* Parse the cell so we can start cell validation. */
+ if (trn_cell_introduce1_parse(&cell, payload, payload_len) < 0) {
+ log_info(LD_PROTOCOL, "Unable to parse INTRODUCE2 cell on circuit %u "
+ "for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto err;
+ }
+
+ /* Success. */
+ *cell_ptr_out = cell;
+ return 0;
+ err:
+ return -1;
+}
+
+/* Set the onion public key onion_pk in cell, the encrypted section of an
+ * INTRODUCE1 cell. */
+static void
+introduce1_set_encrypted_onion_key(trn_cell_introduce_encrypted_t *cell,
+ const uint8_t *onion_pk)
+{
+ tor_assert(cell);
+ tor_assert(onion_pk);
+ /* There is only one possible key type for a non legacy cell. */
+ trn_cell_introduce_encrypted_set_onion_key_type(cell,
+ HS_CELL_ONION_KEY_TYPE_NTOR);
+ trn_cell_introduce_encrypted_set_onion_key_len(cell, CURVE25519_PUBKEY_LEN);
+ trn_cell_introduce_encrypted_setlen_onion_key(cell, CURVE25519_PUBKEY_LEN);
+ memcpy(trn_cell_introduce_encrypted_getarray_onion_key(cell), onion_pk,
+ trn_cell_introduce_encrypted_getlen_onion_key(cell));
+}
+
+/* Set the link specifiers in lspecs in cell, the encrypted section of an
+ * INTRODUCE1 cell. */
+static void
+introduce1_set_encrypted_link_spec(trn_cell_introduce_encrypted_t *cell,
+ const smartlist_t *lspecs)
+{
+ tor_assert(cell);
+ tor_assert(lspecs);
+ tor_assert(smartlist_len(lspecs) > 0);
+ tor_assert(smartlist_len(lspecs) <= UINT8_MAX);
+
+ uint8_t lspecs_num = (uint8_t) smartlist_len(lspecs);
+ trn_cell_introduce_encrypted_set_nspec(cell, lspecs_num);
+ /* We aren't duplicating the link specifiers object here which means that
+ * the ownership goes to the trn_cell_introduce_encrypted_t cell and those
+ * object will be freed when the cell is. */
+ SMARTLIST_FOREACH(lspecs, link_specifier_t *, ls,
+ trn_cell_introduce_encrypted_add_nspecs(cell, ls));
+}
+
+/* Set padding in the enc_cell only if needed that is the total length of both
+ * sections are below the mininum required for an INTRODUCE1 cell. */
+static void
+introduce1_set_encrypted_padding(const trn_cell_introduce1_t *cell,
+ trn_cell_introduce_encrypted_t *enc_cell)
+{
+ tor_assert(cell);
+ tor_assert(enc_cell);
+ /* This is the length we expect to have once encoded of the whole cell. */
+ ssize_t full_len = trn_cell_introduce1_encoded_len(cell) +
+ trn_cell_introduce_encrypted_encoded_len(enc_cell);
+ tor_assert(full_len > 0);
+ if (full_len < HS_CELL_INTRODUCE1_MIN_SIZE) {
+ size_t padding = HS_CELL_INTRODUCE1_MIN_SIZE - full_len;
+ trn_cell_introduce_encrypted_setlen_pad(enc_cell, padding);
+ memset(trn_cell_introduce_encrypted_getarray_pad(enc_cell), 0,
+ trn_cell_introduce_encrypted_getlen_pad(enc_cell));
+ }
+}
+
+/* Encrypt the ENCRYPTED payload and encode it in the cell using the enc_cell
+ * and the INTRODUCE1 data.
+ *
+ * This can't fail but it is very important that the caller sets every field
+ * in data so the computation of the INTRODUCE1 keys doesn't fail. */
+static void
+introduce1_encrypt_and_encode(trn_cell_introduce1_t *cell,
+ const trn_cell_introduce_encrypted_t *enc_cell,
+ const hs_cell_introduce1_data_t *data)
+{
+ size_t offset = 0;
+ ssize_t encrypted_len;
+ ssize_t encoded_cell_len, encoded_enc_cell_len;
+ uint8_t encoded_cell[RELAY_PAYLOAD_SIZE] = {0};
+ uint8_t encoded_enc_cell[RELAY_PAYLOAD_SIZE] = {0};
+ uint8_t *encrypted = NULL;
+ uint8_t mac[DIGEST256_LEN];
+ crypto_cipher_t *cipher = NULL;
+ hs_ntor_intro_cell_keys_t keys;
+
+ tor_assert(cell);
+ tor_assert(enc_cell);
+ tor_assert(data);
+
+ /* Encode the cells up to now of what we have to we can perform the MAC
+ * computation on it. */
+ encoded_cell_len = trn_cell_introduce1_encode(encoded_cell,
+ sizeof(encoded_cell), cell);
+ /* We have a much more serious issue if this isn't true. */
+ tor_assert(encoded_cell_len > 0);
+
+ encoded_enc_cell_len =
+ trn_cell_introduce_encrypted_encode(encoded_enc_cell,
+ sizeof(encoded_enc_cell), enc_cell);
+ /* We have a much more serious issue if this isn't true. */
+ tor_assert(encoded_enc_cell_len > 0);
+
+ /* Get the key material for the encryption. */
+ if (hs_ntor_client_get_introduce1_keys(data->auth_pk, data->enc_pk,
+ data->client_kp,
+ data->subcredential, &keys) < 0) {
+ tor_assert_unreached();
+ }
+
+ /* Prepare cipher with the encryption key just computed. */
+ cipher = crypto_cipher_new_with_bits((const char *) keys.enc_key,
+ sizeof(keys.enc_key) * 8);
+ tor_assert(cipher);
+
+ /* Compute the length of the ENCRYPTED section which is the CLIENT_PK,
+ * ENCRYPTED_DATA and MAC length. */
+ encrypted_len = sizeof(data->client_kp->pubkey) + encoded_enc_cell_len +
+ sizeof(mac);
+ tor_assert(encrypted_len < RELAY_PAYLOAD_SIZE);
+ encrypted = tor_malloc_zero(encrypted_len);
+
+ /* Put the CLIENT_PK first. */
+ memcpy(encrypted, data->client_kp->pubkey.public_key,
+ sizeof(data->client_kp->pubkey.public_key));
+ offset += sizeof(data->client_kp->pubkey.public_key);
+ /* Then encrypt and set the ENCRYPTED_DATA. This can't fail. */
+ crypto_cipher_encrypt(cipher, (char *) encrypted + offset,
+ (const char *) encoded_enc_cell, encoded_enc_cell_len);
+ crypto_cipher_free(cipher);
+ offset += encoded_enc_cell_len;
+ /* Compute MAC from the above and put it in the buffer. This function will
+ * make the adjustment to the encryptled_len to ommit the MAC length. */
+ compute_introduce_mac(encoded_cell, encoded_cell_len,
+ encrypted, encrypted_len,
+ keys.mac_key, sizeof(keys.mac_key),
+ mac, sizeof(mac));
+ memcpy(encrypted + offset, mac, sizeof(mac));
+ offset += sizeof(mac);
+ tor_assert(offset == (size_t) encrypted_len);
+
+ /* Set the ENCRYPTED section in the cell. */
+ trn_cell_introduce1_setlen_encrypted(cell, encrypted_len);
+ memcpy(trn_cell_introduce1_getarray_encrypted(cell),
+ encrypted, encrypted_len);
+
+ /* Cleanup. */
+ memwipe(&keys, 0, sizeof(keys));
+ memwipe(mac, 0, sizeof(mac));
+ memwipe(encrypted, 0, sizeof(encrypted_len));
+ memwipe(encoded_enc_cell, 0, sizeof(encoded_enc_cell));
+ tor_free(encrypted);
+}
+
+/* Using the INTRODUCE1 data, setup the ENCRYPTED section in cell. This means
+ * set it, encrypt it and encode it. */
+static void
+introduce1_set_encrypted(trn_cell_introduce1_t *cell,
+ const hs_cell_introduce1_data_t *data)
+{
+ trn_cell_introduce_encrypted_t *enc_cell;
+ trn_cell_extension_t *ext;
+
+ tor_assert(cell);
+ tor_assert(data);
+
+ enc_cell = trn_cell_introduce_encrypted_new();
+ tor_assert(enc_cell);
+
+ /* Set extension data. None are used. */
+ ext = trn_cell_extension_new();
+ tor_assert(ext);
+ trn_cell_extension_set_num(ext, 0);
+ trn_cell_introduce_encrypted_set_extensions(enc_cell, ext);
+
+ /* Set the rendezvous cookie. */
+ memcpy(trn_cell_introduce_encrypted_getarray_rend_cookie(enc_cell),
+ data->rendezvous_cookie, REND_COOKIE_LEN);
+
+ /* Set the onion public key. */
+ introduce1_set_encrypted_onion_key(enc_cell, data->onion_pk->public_key);
+
+ /* Set the link specifiers. */
+ introduce1_set_encrypted_link_spec(enc_cell, data->link_specifiers);
+
+ /* Set padding. */
+ introduce1_set_encrypted_padding(cell, enc_cell);
+
+ /* Encrypt and encode it in the cell. */
+ introduce1_encrypt_and_encode(cell, enc_cell, data);
+
+ /* Cleanup. */
+ trn_cell_introduce_encrypted_free(enc_cell);
+}
+
+/* Set the authentication key in the INTRODUCE1 cell from the given data. */
+static void
+introduce1_set_auth_key(trn_cell_introduce1_t *cell,
+ const hs_cell_introduce1_data_t *data)
+{
+ tor_assert(cell);
+ tor_assert(data);
+ /* There is only one possible type for a non legacy cell. */
+ trn_cell_introduce1_set_auth_key_type(cell, HS_INTRO_AUTH_KEY_TYPE_ED25519);
+ trn_cell_introduce1_set_auth_key_len(cell, ED25519_PUBKEY_LEN);
+ trn_cell_introduce1_setlen_auth_key(cell, ED25519_PUBKEY_LEN);
+ memcpy(trn_cell_introduce1_getarray_auth_key(cell),
+ data->auth_pk->pubkey, trn_cell_introduce1_getlen_auth_key(cell));
+}
+
+/* Set the legacy ID field in the INTRODUCE1 cell from the given data. */
+static void
+introduce1_set_legacy_id(trn_cell_introduce1_t *cell,
+ const hs_cell_introduce1_data_t *data)
+{
+ tor_assert(cell);
+ tor_assert(data);
+
+ if (data->is_legacy) {
+ uint8_t digest[DIGEST_LEN];
+ if (BUG(crypto_pk_get_digest(data->legacy_key, (char *) digest) < 0)) {
+ return;
+ }
+ memcpy(trn_cell_introduce1_getarray_legacy_key_id(cell),
+ digest, trn_cell_introduce1_getlen_legacy_key_id(cell));
+ } else {
+ /* We have to zeroed the LEGACY_KEY_ID field. */
+ memset(trn_cell_introduce1_getarray_legacy_key_id(cell), 0,
+ trn_cell_introduce1_getlen_legacy_key_id(cell));
+ }
+}
+
+/* ========== */
+/* Public API */
+/* ========== */
+
+/* Build an ESTABLISH_INTRO cell with the given circuit nonce and intro point
+ * object. The encoded cell is put in cell_out that MUST at least be of the
+ * size of RELAY_PAYLOAD_SIZE. Return the encoded cell length on success else
+ * a negative value and cell_out is untouched. This function also supports
+ * legacy cell creation. */
+ssize_t
+hs_cell_build_establish_intro(const char *circ_nonce,
+ const hs_service_intro_point_t *ip,
+ uint8_t *cell_out)
+{
+ ssize_t cell_len = -1;
+ uint16_t sig_len = ED25519_SIG_LEN;
+ trn_cell_extension_t *ext;
+ trn_cell_establish_intro_t *cell = NULL;
+
+ tor_assert(circ_nonce);
+ tor_assert(ip);
+
+ /* Quickly handle the legacy IP. */
+ if (ip->base.is_only_legacy) {
+ tor_assert(ip->legacy_key);
+ cell_len = build_legacy_establish_intro(circ_nonce, ip->legacy_key,
+ cell_out);
+ tor_assert(cell_len <= RELAY_PAYLOAD_SIZE);
+ /* Success or not we are done here. */
+ goto done;
+ }
+
+ /* Set extension data. None used here. */
+ ext = trn_cell_extension_new();
+ trn_cell_extension_set_num(ext, 0);
+ cell = trn_cell_establish_intro_new();
+ trn_cell_establish_intro_set_extensions(cell, ext);
+ /* Set signature size. Array is then allocated in the cell. We need to do
+ * this early so we can use trunnel API to get the signature length. */
+ trn_cell_establish_intro_set_sig_len(cell, sig_len);
+ trn_cell_establish_intro_setlen_sig(cell, sig_len);
+
+ /* Set AUTH_KEY_TYPE: 2 means ed25519 */
+ trn_cell_establish_intro_set_auth_key_type(cell,
+ HS_INTRO_AUTH_KEY_TYPE_ED25519);
+
+ /* Set AUTH_KEY and AUTH_KEY_LEN field. Must also set byte-length of
+ * AUTH_KEY to match */
+ {
+ uint16_t auth_key_len = ED25519_PUBKEY_LEN;
+ trn_cell_establish_intro_set_auth_key_len(cell, auth_key_len);
+ trn_cell_establish_intro_setlen_auth_key(cell, auth_key_len);
+ /* We do this call _after_ setting the length because it's reallocated at
+ * that point only. */
+ uint8_t *auth_key_ptr = trn_cell_establish_intro_getarray_auth_key(cell);
+ memcpy(auth_key_ptr, ip->auth_key_kp.pubkey.pubkey, auth_key_len);
+ }
+
+ /* Calculate HANDSHAKE_AUTH field (MAC). */
+ {
+ ssize_t tmp_cell_enc_len = 0;
+ ssize_t tmp_cell_mac_offset =
+ sig_len + sizeof(cell->sig_len) +
+ trn_cell_establish_intro_getlen_handshake_mac(cell);
+ uint8_t tmp_cell_enc[RELAY_PAYLOAD_SIZE] = {0};
+ uint8_t mac[TRUNNEL_SHA3_256_LEN], *handshake_ptr;
+
+ /* We first encode the current fields we have in the cell so we can
+ * compute the MAC using the raw bytes. */
+ tmp_cell_enc_len = trn_cell_establish_intro_encode(tmp_cell_enc,
+ sizeof(tmp_cell_enc),
+ cell);
+ if (BUG(tmp_cell_enc_len < 0)) {
+ goto done;
+ }
+ /* Sanity check. */
+ tor_assert(tmp_cell_enc_len > tmp_cell_mac_offset);
+
+ /* Circuit nonce is always DIGEST_LEN according to tor-spec.txt. */
+ crypto_mac_sha3_256(mac, sizeof(mac),
+ (uint8_t *) circ_nonce, DIGEST_LEN,
+ tmp_cell_enc, tmp_cell_enc_len - tmp_cell_mac_offset);
+ handshake_ptr = trn_cell_establish_intro_getarray_handshake_mac(cell);
+ memcpy(handshake_ptr, mac, sizeof(mac));
+
+ memwipe(mac, 0, sizeof(mac));
+ memwipe(tmp_cell_enc, 0, sizeof(tmp_cell_enc));
+ }
+
+ /* Calculate the cell signature SIG. */
+ {
+ ssize_t tmp_cell_enc_len = 0;
+ ssize_t tmp_cell_sig_offset = (sig_len + sizeof(cell->sig_len));
+ uint8_t tmp_cell_enc[RELAY_PAYLOAD_SIZE] = {0}, *sig_ptr;
+ ed25519_signature_t sig;
+
+ /* We first encode the current fields we have in the cell so we can
+ * compute the signature from the raw bytes of the cell. */
+ tmp_cell_enc_len = trn_cell_establish_intro_encode(tmp_cell_enc,
+ sizeof(tmp_cell_enc),
+ cell);
+ if (BUG(tmp_cell_enc_len < 0)) {
+ goto done;
+ }
+
+ if (ed25519_sign_prefixed(&sig, tmp_cell_enc,
+ tmp_cell_enc_len - tmp_cell_sig_offset,
+ ESTABLISH_INTRO_SIG_PREFIX, &ip->auth_key_kp)) {
+ log_warn(LD_BUG, "Unable to make signature for ESTABLISH_INTRO cell.");
+ goto done;
+ }
+ /* Copy the signature into the cell. */
+ sig_ptr = trn_cell_establish_intro_getarray_sig(cell);
+ memcpy(sig_ptr, sig.sig, sig_len);
+
+ memwipe(tmp_cell_enc, 0, sizeof(tmp_cell_enc));
+ }
+
+ /* Encode the cell. Can't be bigger than a standard cell. */
+ cell_len = trn_cell_establish_intro_encode(cell_out, RELAY_PAYLOAD_SIZE,
+ cell);
+
+ done:
+ trn_cell_establish_intro_free(cell);
+ return cell_len;
+}
+
+/* Parse the INTRO_ESTABLISHED cell in the payload of size payload_len. If we
+ * are successful at parsing it, return the length of the parsed cell else a
+ * negative value on error. */
+ssize_t
+hs_cell_parse_intro_established(const uint8_t *payload, size_t payload_len)
+{
+ ssize_t ret;
+ trn_cell_intro_established_t *cell = NULL;
+
+ tor_assert(payload);
+
+ /* Try to parse the payload into a cell making sure we do actually have a
+ * valid cell. */
+ ret = trn_cell_intro_established_parse(&cell, payload, payload_len);
+ if (ret >= 0) {
+ /* On success, we do not keep the cell, we just notify the caller that it
+ * was successfully parsed. */
+ trn_cell_intro_established_free(cell);
+ }
+ return ret;
+}
+
+/* Parsse 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
+ * service and circ are only used for logging purposes. */
+ssize_t
+hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data,
+ const origin_circuit_t *circ,
+ const hs_service_t *service)
+{
+ int ret = -1;
+ time_t elapsed;
+ uint8_t *decrypted = NULL;
+ size_t encrypted_section_len;
+ const uint8_t *encrypted_section;
+ trn_cell_introduce1_t *cell = NULL;
+ trn_cell_introduce_encrypted_t *enc_cell = NULL;
+ hs_ntor_intro_cell_keys_t *intro_keys = NULL;
+
+ tor_assert(data);
+ tor_assert(circ);
+ tor_assert(service);
+
+ /* Parse the cell into a decoded data structure pointed by cell_ptr. */
+ if (parse_introduce2_cell(service, circ, data->payload, data->payload_len,
+ &cell) < 0) {
+ goto done;
+ }
+
+ log_info(LD_REND, "Received a decodable INTRODUCE2 cell on circuit %u "
+ "for service %s. Decoding encrypted section...",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+
+ encrypted_section = trn_cell_introduce1_getconstarray_encrypted(cell);
+ encrypted_section_len = trn_cell_introduce1_getlen_encrypted(cell);
+
+ /* Encrypted section must at least contain the CLIENT_PK and MAC which is
+ * defined in section 3.3.2 of the specification. */
+ if (encrypted_section_len < (CURVE25519_PUBKEY_LEN + DIGEST256_LEN)) {
+ log_info(LD_REND, "Invalid INTRODUCE2 encrypted section length "
+ "for service %s. Dropping cell.",
+ safe_str_client(service->onion_address));
+ goto done;
+ }
+
+ /* 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"
+ "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,
+ 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 =
+ encrypted_section + sizeof(data->client_pk);
+ /* It's symmetric encryption so it's correct to use the ENCRYPTED length
+ * for decryption. Computes the length of ENCRYPTED_DATA meaning removing
+ * the CLIENT_PK and MAC length. */
+ size_t encrypted_data_len =
+ encrypted_section_len - (sizeof(data->client_pk) + DIGEST256_LEN);
+
+ /* This decrypts the ENCRYPTED_DATA section of the cell. */
+ decrypted = decrypt_introduce2(intro_keys->enc_key,
+ encrypted_data, encrypted_data_len);
+ if (decrypted == NULL) {
+ log_info(LD_REND, "Unable to decrypt the ENCRYPTED section of an "
+ "INTRODUCE2 cell on circuit %u for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto done;
+ }
+
+ /* Parse this blob into an encrypted cell structure so we can then extract
+ * the data we need out of it. */
+ enc_cell = parse_introduce2_encrypted(decrypted, encrypted_data_len,
+ circ, service);
+ memwipe(decrypted, 0, encrypted_data_len);
+ if (enc_cell == NULL) {
+ goto done;
+ }
+ }
+
+ /* XXX: Implement client authorization checks. */
+
+ /* Extract onion key and rendezvous cookie from the cell used for the
+ * rendezvous point circuit e2e encryption. */
+ memcpy(data->onion_pk.public_key,
+ trn_cell_introduce_encrypted_getconstarray_onion_key(enc_cell),
+ CURVE25519_PUBKEY_LEN);
+ memcpy(data->rendezvous_cookie,
+ trn_cell_introduce_encrypted_getconstarray_rend_cookie(enc_cell),
+ sizeof(data->rendezvous_cookie));
+
+ /* Extract rendezvous link specifiers. */
+ for (size_t idx = 0;
+ idx < trn_cell_introduce_encrypted_get_nspec(enc_cell); idx++) {
+ link_specifier_t *lspec =
+ trn_cell_introduce_encrypted_get_nspecs(enc_cell, idx);
+ smartlist_add(data->link_specifiers, hs_link_specifier_dup(lspec));
+ }
+
+ /* Success. */
+ ret = 0;
+ log_info(LD_REND, "Valid INTRODUCE2 cell. Launching rendezvous circuit.");
+
+ done:
+ if (intro_keys) {
+ memwipe(intro_keys, 0, sizeof(hs_ntor_intro_cell_keys_t));
+ tor_free(intro_keys);
+ }
+ tor_free(decrypted);
+ trn_cell_introduce_encrypted_free(enc_cell);
+ trn_cell_introduce1_free(cell);
+ return ret;
+}
+
+/* Build a RENDEZVOUS1 cell with the given rendezvous cookie and handshake
+ * info. The encoded cell is put in cell_out and the length of the data is
+ * returned. This can't fail. */
+ssize_t
+hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie,
+ size_t rendezvous_cookie_len,
+ const uint8_t *rendezvous_handshake_info,
+ size_t rendezvous_handshake_info_len,
+ uint8_t *cell_out)
+{
+ ssize_t cell_len;
+ trn_cell_rendezvous1_t *cell;
+
+ tor_assert(rendezvous_cookie);
+ tor_assert(rendezvous_handshake_info);
+ tor_assert(cell_out);
+
+ cell = trn_cell_rendezvous1_new();
+ /* Set the RENDEZVOUS_COOKIE. */
+ memcpy(trn_cell_rendezvous1_getarray_rendezvous_cookie(cell),
+ rendezvous_cookie, rendezvous_cookie_len);
+ /* Set the HANDSHAKE_INFO. */
+ trn_cell_rendezvous1_setlen_handshake_info(cell,
+ rendezvous_handshake_info_len);
+ memcpy(trn_cell_rendezvous1_getarray_handshake_info(cell),
+ rendezvous_handshake_info, rendezvous_handshake_info_len);
+ /* Encoding. */
+ cell_len = trn_cell_rendezvous1_encode(cell_out, RELAY_PAYLOAD_SIZE, cell);
+ tor_assert(cell_len > 0);
+
+ trn_cell_rendezvous1_free(cell);
+ return cell_len;
+}
+
+/* Build an INTRODUCE1 cell from the given data. The encoded cell is put in
+ * cell_out which must be of at least size RELAY_PAYLOAD_SIZE. On success, the
+ * encoded length is returned else a negative value and the content of
+ * cell_out should be ignored. */
+ssize_t
+hs_cell_build_introduce1(const hs_cell_introduce1_data_t *data,
+ uint8_t *cell_out)
+{
+ ssize_t cell_len;
+ trn_cell_introduce1_t *cell;
+ trn_cell_extension_t *ext;
+
+ tor_assert(data);
+ tor_assert(cell_out);
+
+ cell = trn_cell_introduce1_new();
+ tor_assert(cell);
+
+ /* Set extension data. None are used. */
+ ext = trn_cell_extension_new();
+ tor_assert(ext);
+ trn_cell_extension_set_num(ext, 0);
+ trn_cell_introduce1_set_extensions(cell, ext);
+
+ /* Set the legacy ID field. */
+ introduce1_set_legacy_id(cell, data);
+
+ /* Set the authentication key. */
+ introduce1_set_auth_key(cell, data);
+
+ /* Set the encrypted section. This will set, encrypt and encode the
+ * ENCRYPTED section in the cell. After this, we'll be ready to encode. */
+ introduce1_set_encrypted(cell, data);
+
+ /* Final encoding. */
+ cell_len = trn_cell_introduce1_encode(cell_out, RELAY_PAYLOAD_SIZE, cell);
+
+ trn_cell_introduce1_free(cell);
+ return cell_len;
+}
+
+/* Build an ESTABLISH_RENDEZVOUS cell from the given rendezvous_cookie. The
+ * encoded cell is put in cell_out which must be of at least
+ * RELAY_PAYLOAD_SIZE. On success, the encoded length is returned and the
+ * caller should clear up the content of the cell.
+ *
+ * This function can't fail. */
+ssize_t
+hs_cell_build_establish_rendezvous(const uint8_t *rendezvous_cookie,
+ uint8_t *cell_out)
+{
+ tor_assert(rendezvous_cookie);
+ tor_assert(cell_out);
+
+ memcpy(cell_out, rendezvous_cookie, HS_REND_COOKIE_LEN);
+ return HS_REND_COOKIE_LEN;
+}
+
+/* Handle an INTRODUCE_ACK cell encoded in payload of length payload_len.
+ * Return the status code on success else a negative value if the cell as not
+ * decodable. */
+int
+hs_cell_parse_introduce_ack(const uint8_t *payload, size_t payload_len)
+{
+ int ret = -1;
+ trn_cell_introduce_ack_t *cell = NULL;
+
+ tor_assert(payload);
+
+ /* If it is a legacy IP, rend-spec.txt specifies that a ACK is 0 byte and a
+ * NACK is 1 byte. We can't use the legacy function for this so we have to
+ * do a special case. */
+ if (payload_len <= 1) {
+ if (payload_len == 0) {
+ ret = HS_CELL_INTRO_ACK_SUCCESS;
+ } else {
+ ret = HS_CELL_INTRO_ACK_FAILURE;
+ }
+ goto end;
+ }
+
+ if (trn_cell_introduce_ack_parse(&cell, payload, payload_len) < 0) {
+ log_info(LD_REND, "Invalid INTRODUCE_ACK cell. Unable to parse it.");
+ goto end;
+ }
+
+ ret = trn_cell_introduce_ack_get_status(cell);
+
+ end:
+ trn_cell_introduce_ack_free(cell);
+ return ret;
+}
+
+/* Handle a RENDEZVOUS2 cell encoded in payload of length payload_len. On
+ * success, handshake_info contains the data in the HANDSHAKE_INFO field, and
+ * 0 is returned. On error, a negative value is returned. */
+int
+hs_cell_parse_rendezvous2(const uint8_t *payload, size_t payload_len,
+ uint8_t *handshake_info, size_t handshake_info_len)
+{
+ int ret = -1;
+ trn_cell_rendezvous2_t *cell = NULL;
+
+ tor_assert(payload);
+ tor_assert(handshake_info);
+
+ if (trn_cell_rendezvous2_parse(&cell, payload, payload_len) < 0) {
+ log_info(LD_REND, "Invalid RENDEZVOUS2 cell. Unable to parse it.");
+ goto end;
+ }
+
+ /* Static size, we should never have an issue with this else we messed up
+ * our code flow. */
+ tor_assert(trn_cell_rendezvous2_getlen_handshake_info(cell) ==
+ handshake_info_len);
+ memcpy(handshake_info,
+ trn_cell_rendezvous2_getconstarray_handshake_info(cell),
+ handshake_info_len);
+ ret = 0;
+
+ end:
+ trn_cell_rendezvous2_free(cell);
+ return ret;
+}
+
+/* Clear the given INTRODUCE1 data structure data. */
+void
+hs_cell_introduce1_data_clear(hs_cell_introduce1_data_t *data)
+{
+ if (data == NULL) {
+ return;
+ }
+ /* Object in this list have been moved to the cell object when building it
+ * so they've been freed earlier. We do that in order to avoid duplicating
+ * them leading to more memory and CPU time being used for nothing. */
+ smartlist_free(data->link_specifiers);
+ /* The data object has no ownership of any members. */
+ memwipe(data, 0, sizeof(hs_cell_introduce1_data_t));
+}
+
diff --git a/src/or/hs_cell.h b/src/or/hs_cell.h
new file mode 100644
index 0000000000..5136fce933
--- /dev/null
+++ b/src/or/hs_cell.h
@@ -0,0 +1,122 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_cell.h
+ * \brief Header file containing cell data for the whole HS subsytem.
+ **/
+
+#ifndef TOR_HS_CELL_H
+#define TOR_HS_CELL_H
+
+#include "or.h"
+#include "hs_service.h"
+
+/* An INTRODUCE1 cell requires at least this amount of bytes (see section
+ * 3.2.2 of the specification). Below this value, the cell must be padded. */
+#define HS_CELL_INTRODUCE1_MIN_SIZE 246
+
+/* Status code of an INTRODUCE_ACK cell. */
+typedef enum {
+ HS_CELL_INTRO_ACK_SUCCESS = 0x0000, /* Cell relayed to service. */
+ HS_CELL_INTRO_ACK_FAILURE = 0x0001, /* Service ID not recognized */
+ HS_CELL_INTRO_ACK_BADFMT = 0x0002, /* Bad message format */
+ HS_CELL_INTRO_ACK_NORELAY = 0x0003, /* Can't relay cell to service */
+} hs_cell_introd_ack_status_t;
+
+/* Onion key type found in the INTRODUCE1 cell. */
+typedef enum {
+ HS_CELL_ONION_KEY_TYPE_NTOR = 1,
+} hs_cell_onion_key_type_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 {
+ /* Is this a legacy introduction point? */
+ unsigned int is_legacy : 1;
+ /* (Legacy only) The encryption key for a legacy intro point. Only set if
+ * is_legacy is true. */
+ const crypto_pk_t *legacy_key;
+ /* Introduction point authentication public key. */
+ const ed25519_public_key_t *auth_pk;
+ /* Introduction point encryption public key. */
+ const curve25519_public_key_t *enc_pk;
+ /* Subcredentials of the service. */
+ const uint8_t *subcredential;
+ /* Onion public key for the ntor handshake. */
+ const curve25519_public_key_t *onion_pk;
+ /* Rendezvous cookie. */
+ const uint8_t *rendezvous_cookie;
+ /* Public key put before the encrypted data (CLIENT_PK). */
+ const curve25519_keypair_t *client_kp;
+ /* Rendezvous point link specifiers. */
+ smartlist_t *link_specifiers;
+} hs_cell_introduce1_data_t;
+
+/* This data structure contains data that we need to parse an INTRODUCE2 cell
+ * which is used by the INTRODUCE2 cell parsing function. On a successful
+ * parsing, the onion_pk and rendezvous_cookie will be populated with the
+ * computed key material from the cell data. This structure is only used during
+ * INTRO2 parsing and discarded after that. */
+typedef struct hs_cell_introduce2_data_t {
+ /*** Immutable Section: Set on structure init. ***/
+
+ /* Introduction point authentication public key. Pointer owned by the
+ introduction point object through which we received the INTRO2 cell. */
+ const ed25519_public_key_t *auth_pk;
+ /* Introduction point encryption keypair for the ntor handshake. Pointer
+ 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;
+ /* Payload of the received encoded cell. */
+ const uint8_t *payload;
+ /* Size of the payload of the received encoded cell. */
+ size_t payload_len;
+
+ /*** Mutable Section: Set upon parsing INTRODUCE2 cell. ***/
+
+ /* Onion public key computed using the INTRODUCE2 encrypted section. */
+ curve25519_public_key_t onion_pk;
+ /* Rendezvous cookie taken from the INTRODUCE2 encrypted section. */
+ uint8_t rendezvous_cookie[REND_COOKIE_LEN];
+ /* Client public key from the INTRODUCE2 encrypted section. */
+ curve25519_public_key_t client_pk;
+ /* Link specifiers of the rendezvous point. Contains link_specifier_t. */
+ smartlist_t *link_specifiers;
+ /* Replay cache of the introduction point. */
+ replaycache_t *replay_cache;
+} hs_cell_introduce2_data_t;
+
+/* Build cell API. */
+ssize_t hs_cell_build_establish_intro(const char *circ_nonce,
+ const hs_service_intro_point_t *ip,
+ uint8_t *cell_out);
+ssize_t hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie,
+ size_t rendezvous_cookie_len,
+ const uint8_t *rendezvous_handshake_info,
+ size_t rendezvous_handshake_info_len,
+ uint8_t *cell_out);
+ssize_t hs_cell_build_introduce1(const hs_cell_introduce1_data_t *data,
+ uint8_t *cell_out);
+ssize_t hs_cell_build_establish_rendezvous(const uint8_t *rendezvous_cookie,
+ uint8_t *cell_out);
+
+/* Parse cell API. */
+ssize_t hs_cell_parse_intro_established(const uint8_t *payload,
+ size_t payload_len);
+ssize_t hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data,
+ const origin_circuit_t *circ,
+ const hs_service_t *service);
+int hs_cell_parse_introduce_ack(const uint8_t *payload, size_t payload_len);
+int hs_cell_parse_rendezvous2(const uint8_t *payload, size_t payload_len,
+ uint8_t *handshake_info,
+ size_t handshake_info_len);
+
+/* Util API. */
+void hs_cell_introduce1_data_clear(hs_cell_introduce1_data_t *data);
+
+#endif /* TOR_HS_CELL_H */
+
diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c
new file mode 100644
index 0000000000..2bfeac747c
--- /dev/null
+++ b/src/or/hs_circuit.c
@@ -0,0 +1,1133 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_circuit.c
+ **/
+
+#include "or.h"
+#include "circpathbias.h"
+#include "circuitbuild.h"
+#include "circuitlist.h"
+#include "circuituse.h"
+#include "config.h"
+#include "policies.h"
+#include "relay.h"
+#include "rendservice.h"
+#include "rephist.h"
+#include "router.h"
+
+#include "hs_cell.h"
+#include "hs_circuit.h"
+#include "hs_ident.h"
+#include "hs_ntor.h"
+#include "hs_service.h"
+
+/* Trunnel. */
+#include "ed25519_cert.h"
+#include "hs/cell_common.h"
+#include "hs/cell_establish_intro.h"
+
+/* A circuit is about to become an e2e rendezvous circuit. Check
+ * <b>circ_purpose</b> and ensure that it's properly set. Return true iff
+ * circuit purpose is properly set, otherwise return false. */
+static int
+circuit_purpose_is_correct_for_rend(unsigned int circ_purpose,
+ int is_service_side)
+{
+ if (is_service_side) {
+ if (circ_purpose != CIRCUIT_PURPOSE_S_CONNECT_REND) {
+ log_warn(LD_BUG,
+ "HS e2e circuit setup with wrong purpose (%d)", circ_purpose);
+ return 0;
+ }
+ }
+
+ if (!is_service_side) {
+ if (circ_purpose != CIRCUIT_PURPOSE_C_REND_READY &&
+ circ_purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) {
+ log_warn(LD_BUG,
+ "Client e2e circuit setup with wrong purpose (%d)", circ_purpose);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+/* Create and return a crypt path for the final hop of a v3 prop224 rendezvous
+ * circuit. Initialize the crypt path crypto using the output material from the
+ * ntor key exchange at <b>ntor_key_seed</b>.
+ *
+ * If <b>is_service_side</b> is set, we are the hidden service and the final
+ * hop of the rendezvous circuit is the client on the other side. */
+static crypt_path_t *
+create_rend_cpath(const uint8_t *ntor_key_seed, size_t seed_len,
+ int is_service_side)
+{
+ uint8_t keys[HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN];
+ crypt_path_t *cpath = NULL;
+
+ /* Do the key expansion */
+ if (hs_ntor_circuit_key_expansion(ntor_key_seed, seed_len,
+ keys, sizeof(keys)) < 0) {
+ goto err;
+ }
+
+ /* Setup the cpath */
+ cpath = tor_malloc_zero(sizeof(crypt_path_t));
+ cpath->magic = CRYPT_PATH_MAGIC;
+
+ if (circuit_init_cpath_crypto(cpath, (char*)keys, sizeof(keys),
+ is_service_side, 1) < 0) {
+ tor_free(cpath);
+ goto err;
+ }
+
+ err:
+ memwipe(keys, 0, sizeof(keys));
+ return cpath;
+}
+
+/* We are a v2 legacy HS client: Create and return a crypt path for the hidden
+ * service on the other side of the rendezvous circuit <b>circ</b>. Initialize
+ * the crypt path crypto using the body of the RENDEZVOUS1 cell at
+ * <b>rend_cell_body</b> (which must be at least DH_KEY_LEN+DIGEST_LEN bytes).
+ */
+static crypt_path_t *
+create_rend_cpath_legacy(origin_circuit_t *circ, const uint8_t *rend_cell_body)
+{
+ crypt_path_t *hop = NULL;
+ char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN];
+
+ /* first DH_KEY_LEN bytes are g^y from the service. Finish the dh
+ * handshake...*/
+ tor_assert(circ->build_state);
+ tor_assert(circ->build_state->pending_final_cpath);
+ hop = circ->build_state->pending_final_cpath;
+
+ tor_assert(hop->rend_dh_handshake_state);
+ if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN, hop->rend_dh_handshake_state,
+ (char*)rend_cell_body, DH_KEY_LEN,
+ keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) {
+ log_warn(LD_GENERAL, "Couldn't complete DH handshake.");
+ goto err;
+ }
+ /* ... and set up cpath. */
+ if (circuit_init_cpath_crypto(hop,
+ keys+DIGEST_LEN, sizeof(keys)-DIGEST_LEN,
+ 0, 0) < 0)
+ goto err;
+
+ /* Check whether the digest is right... */
+ if (tor_memneq(keys, rend_cell_body+DH_KEY_LEN, DIGEST_LEN)) {
+ log_warn(LD_PROTOCOL, "Incorrect digest of key material.");
+ goto err;
+ }
+
+ /* clean up the crypto stuff we just made */
+ crypto_dh_free(hop->rend_dh_handshake_state);
+ hop->rend_dh_handshake_state = NULL;
+
+ goto done;
+
+ err:
+ hop = NULL;
+
+ done:
+ memwipe(keys, 0, sizeof(keys));
+ return hop;
+}
+
+/* Append the final <b>hop</b> to the cpath of the rend <b>circ</b>, and mark
+ * <b>circ</b> ready for use to transfer HS relay cells. */
+static void
+finalize_rend_circuit(origin_circuit_t *circ, crypt_path_t *hop,
+ int is_service_side)
+{
+ tor_assert(circ);
+ tor_assert(hop);
+
+ /* Notify the circuit state machine that we are splicing this circuit */
+ int new_circ_purpose = is_service_side ?
+ CIRCUIT_PURPOSE_S_REND_JOINED : CIRCUIT_PURPOSE_C_REND_JOINED;
+ circuit_change_purpose(TO_CIRCUIT(circ), new_circ_purpose);
+
+ /* All is well. Extend the circuit. */
+ hop->state = CPATH_STATE_OPEN;
+ /* Set the windows to default. */
+ hop->package_window = circuit_initial_package_window();
+ hop->deliver_window = CIRCWINDOW_START;
+
+ /* Now that this circuit has finished connecting to its destination,
+ * make sure circuit_get_open_circ_or_launch is willing to return it
+ * so we can actually use it. */
+ circ->hs_circ_has_timed_out = 0;
+
+ /* Append the hop to the cpath of this circuit */
+ onion_append_to_cpath(&circ->cpath, hop);
+
+ /* In legacy code, 'pending_final_cpath' points to the final hop we just
+ * appended to the cpath. We set the original pointer to NULL so that we
+ * don't double free it. */
+ if (circ->build_state) {
+ circ->build_state->pending_final_cpath = NULL;
+ }
+
+ /* Finally, mark circuit as ready to be used for client streams */
+ if (!is_service_side) {
+ circuit_try_attaching_streams(circ);
+ }
+}
+
+/* For a given circuit and a service introduction point object, register the
+ * intro circuit to the circuitmap. This supports legacy intro point. */
+static void
+register_intro_circ(const hs_service_intro_point_t *ip,
+ origin_circuit_t *circ)
+{
+ tor_assert(ip);
+ tor_assert(circ);
+
+ if (ip->base.is_only_legacy) {
+ uint8_t digest[DIGEST_LEN];
+ if (BUG(crypto_pk_get_digest(ip->legacy_key, (char *) digest) < 0)) {
+ return;
+ }
+ hs_circuitmap_register_intro_circ_v2_service_side(circ, digest);
+ } else {
+ hs_circuitmap_register_intro_circ_v3_service_side(circ,
+ &ip->auth_key_kp.pubkey);
+ }
+}
+
+/* Return the number of opened introduction circuit for the given circuit that
+ * is matching its identity key. */
+static unsigned int
+count_opened_desc_intro_point_circuits(const hs_service_t *service,
+ const hs_service_descriptor_t *desc)
+{
+ unsigned int count = 0;
+
+ tor_assert(service);
+ tor_assert(desc);
+
+ DIGEST256MAP_FOREACH(desc->intro_points.map, key,
+ const hs_service_intro_point_t *, ip) {
+ const circuit_t *circ;
+ const origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip);
+ if (ocirc == NULL) {
+ continue;
+ }
+ circ = TO_CIRCUIT(ocirc);
+ tor_assert(circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO ||
+ circ->purpose == CIRCUIT_PURPOSE_S_INTRO);
+ /* Having a circuit not for the requested service is really bad. */
+ tor_assert(ed25519_pubkey_eq(&service->keys.identity_pk,
+ &ocirc->hs_ident->identity_pk));
+ /* Only count opened circuit and skip circuit that will be closed. */
+ if (!circ->marked_for_close && circ->state == CIRCUIT_STATE_OPEN) {
+ count++;
+ }
+ } DIGEST256MAP_FOREACH_END;
+ return count;
+}
+
+/* From a given service, rendezvous cookie and handshake info, create a
+ * rendezvous point circuit identifier. This can't fail. */
+static hs_ident_circuit_t *
+create_rp_circuit_identifier(const hs_service_t *service,
+ const uint8_t *rendezvous_cookie,
+ const curve25519_public_key_t *server_pk,
+ const hs_ntor_rend_cell_keys_t *keys)
+{
+ hs_ident_circuit_t *ident;
+ uint8_t handshake_info[CURVE25519_PUBKEY_LEN + DIGEST256_LEN];
+
+ tor_assert(service);
+ tor_assert(rendezvous_cookie);
+ tor_assert(server_pk);
+ tor_assert(keys);
+
+ ident = hs_ident_circuit_new(&service->keys.identity_pk,
+ HS_IDENT_CIRCUIT_RENDEZVOUS);
+ /* Copy the RENDEZVOUS_COOKIE which is the unique identifier. */
+ memcpy(ident->rendezvous_cookie, rendezvous_cookie,
+ sizeof(ident->rendezvous_cookie));
+ /* Build the HANDSHAKE_INFO which looks like this:
+ * SERVER_PK [32 bytes]
+ * AUTH_INPUT_MAC [32 bytes]
+ */
+ memcpy(handshake_info, server_pk->public_key, CURVE25519_PUBKEY_LEN);
+ memcpy(handshake_info + CURVE25519_PUBKEY_LEN, keys->rend_cell_auth_mac,
+ DIGEST256_LEN);
+ tor_assert(sizeof(ident->rendezvous_handshake_info) ==
+ sizeof(handshake_info));
+ memcpy(ident->rendezvous_handshake_info, handshake_info,
+ sizeof(ident->rendezvous_handshake_info));
+ /* Finally copy the NTOR_KEY_SEED for e2e encryption on the circuit. */
+ tor_assert(sizeof(ident->rendezvous_ntor_key_seed) ==
+ sizeof(keys->ntor_key_seed));
+ memcpy(ident->rendezvous_ntor_key_seed, keys->ntor_key_seed,
+ sizeof(ident->rendezvous_ntor_key_seed));
+ return ident;
+}
+
+/* From a given service and service intro point, create an introduction point
+ * circuit identifier. This can't fail. */
+static hs_ident_circuit_t *
+create_intro_circuit_identifier(const hs_service_t *service,
+ const hs_service_intro_point_t *ip)
+{
+ hs_ident_circuit_t *ident;
+
+ tor_assert(service);
+ tor_assert(ip);
+
+ ident = hs_ident_circuit_new(&service->keys.identity_pk,
+ HS_IDENT_CIRCUIT_INTRO);
+ ed25519_pubkey_copy(&ident->intro_auth_pk, &ip->auth_key_kp.pubkey);
+
+ return ident;
+}
+
+/* For a given introduction point and an introduction circuit, send the
+ * ESTABLISH_INTRO cell. The service object is used for logging. This can fail
+ * and if so, the circuit is closed and the intro point object is flagged
+ * that the circuit is not established anymore which is important for the
+ * retry mechanism. */
+static void
+send_establish_intro(const hs_service_t *service,
+ hs_service_intro_point_t *ip, origin_circuit_t *circ)
+{
+ ssize_t cell_len;
+ uint8_t payload[RELAY_PAYLOAD_SIZE];
+
+ tor_assert(service);
+ tor_assert(ip);
+ tor_assert(circ);
+
+ /* Encode establish intro cell. */
+ cell_len = hs_cell_build_establish_intro(circ->cpath->prev->rend_circ_nonce,
+ ip, payload);
+ if (cell_len < 0) {
+ log_warn(LD_REND, "Unable to encode ESTABLISH_INTRO cell for service %s "
+ "on circuit %u. Closing circuit.",
+ safe_str_client(service->onion_address),
+ TO_CIRCUIT(circ)->n_circ_id);
+ goto err;
+ }
+
+ /* Send the cell on the circuit. */
+ if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ),
+ RELAY_COMMAND_ESTABLISH_INTRO,
+ (char *) payload, cell_len,
+ circ->cpath->prev) < 0) {
+ log_info(LD_REND, "Unable to send ESTABLISH_INTRO cell for service %s "
+ "on circuit %u.",
+ safe_str_client(service->onion_address),
+ TO_CIRCUIT(circ)->n_circ_id);
+ /* On error, the circuit has been closed. */
+ goto done;
+ }
+
+ /* Record the attempt to use this circuit. */
+ pathbias_count_use_attempt(circ);
+ goto done;
+
+ err:
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
+ done:
+ memwipe(payload, 0, sizeof(payload));
+}
+
+/* For a given service, the ntor onion key and a rendezvous cookie, launch a
+ * circuit to the rendezvous point specified by the link specifiers. On
+ * 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)
+{
+ int circ_needs_uptime;
+ time_t now = time(NULL);
+ extend_info_t *info = NULL;
+ origin_circuit_t *circ;
+
+ tor_assert(service);
+ tor_assert(ip);
+ tor_assert(data);
+
+ circ_needs_uptime = hs_service_requires_uptime_circ(service->config.ports);
+
+ /* Get the extend info data structure for the chosen rendezvous point
+ * specified by the given link specifiers. */
+ info = hs_get_extend_info_from_lspecs(data->link_specifiers,
+ &data->onion_pk,
+ service->config.is_single_onion);
+ if (info == NULL) {
+ /* We are done here, we can't extend to the rendezvous point. */
+ goto end;
+ }
+
+ for (int i = 0; i < MAX_REND_FAILURES; i++) {
+ int circ_flags = CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_IS_INTERNAL;
+ if (circ_needs_uptime) {
+ circ_flags |= CIRCLAUNCH_NEED_UPTIME;
+ }
+ /* Firewall and policies are checked when getting the extend info. */
+ if (service->config.is_single_onion) {
+ circ_flags |= CIRCLAUNCH_ONEHOP_TUNNEL;
+ }
+
+ circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND, info,
+ circ_flags);
+ if (circ != NULL) {
+ /* Stop retrying, we have a circuit! */
+ break;
+ }
+ }
+ if (circ == NULL) {
+ log_warn(LD_REND, "Giving up on launching rendezvous circuit to %s "
+ "for service %s",
+ safe_str_client(extend_info_describe(info)),
+ safe_str_client(service->onion_address));
+ goto end;
+ }
+ log_info(LD_REND, "Rendezvous circuit launched to %s with cookie %s "
+ "for service %s",
+ safe_str_client(extend_info_describe(info)),
+ safe_str_client(hex_str((const char *) data->rendezvous_cookie,
+ REND_COOKIE_LEN)),
+ safe_str_client(service->onion_address));
+ tor_assert(circ->build_state);
+ /* Rendezvous circuit have a specific timeout for the time spent on trying
+ * to connect to the rendezvous point. */
+ circ->build_state->expiry_time = now + MAX_REND_TIMEOUT;
+
+ /* Create circuit identifier and key material. */
+ {
+ hs_ntor_rend_cell_keys_t keys;
+ curve25519_keypair_t ephemeral_kp;
+ /* No need for extra strong, this is only for this circuit life time. This
+ * key will be used for the RENDEZVOUS1 cell that will be sent on the
+ * circuit once opened. */
+ curve25519_keypair_generate(&ephemeral_kp, 0);
+ if (hs_ntor_service_get_rendezvous1_keys(&ip->auth_key_kp.pubkey,
+ &ip->enc_key_kp,
+ &ephemeral_kp, &data->client_pk,
+ &keys) < 0) {
+ /* This should not really happened but just in case, don't make tor
+ * freak out, close the circuit and move on. */
+ log_info(LD_REND, "Unable to get RENDEZVOUS1 key material for "
+ "service %s",
+ safe_str_client(service->onion_address));
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
+ goto end;
+ }
+ circ->hs_ident = create_rp_circuit_identifier(service,
+ data->rendezvous_cookie,
+ &ephemeral_kp.pubkey, &keys);
+ memwipe(&ephemeral_kp, 0, sizeof(ephemeral_kp));
+ memwipe(&keys, 0, sizeof(keys));
+ tor_assert(circ->hs_ident);
+ }
+
+ end:
+ extend_info_free(info);
+}
+
+/* Return true iff the given service rendezvous circuit circ is allowed for a
+ * relaunch to the rendezvous point. */
+static int
+can_relaunch_service_rendezvous_point(const origin_circuit_t *circ)
+{
+ tor_assert(circ);
+ /* This is initialized when allocating an origin circuit. */
+ tor_assert(circ->build_state);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
+
+ /* XXX: Retrying under certain condition. This is related to #22455. */
+
+ /* Avoid to relaunch twice a circuit to the same rendezvous point at the
+ * same time. */
+ if (circ->hs_service_side_rend_circ_has_been_relaunched) {
+ log_info(LD_REND, "Rendezvous circuit to %s has already been retried. "
+ "Skipping retry.",
+ safe_str_client(
+ extend_info_describe(circ->build_state->chosen_exit)));
+ goto disallow;
+ }
+
+ /* A failure count that has reached maximum allowed or circuit that expired,
+ * we skip relaunching. */
+ if (circ->build_state->failure_count > MAX_REND_FAILURES ||
+ circ->build_state->expiry_time <= time(NULL)) {
+ log_info(LD_REND, "Attempt to build a rendezvous circuit to %s has "
+ "failed with %d attempts and expiry time %ld. "
+ "Giving up building.",
+ safe_str_client(
+ extend_info_describe(circ->build_state->chosen_exit)),
+ circ->build_state->failure_count,
+ (long int) circ->build_state->expiry_time);
+ goto disallow;
+ }
+
+ /* Allowed to relaunch. */
+ return 1;
+ disallow:
+ return 0;
+}
+
+/* Retry the rendezvous point of circ by launching a new circuit to it. */
+static void
+retry_service_rendezvous_point(const origin_circuit_t *circ)
+{
+ int flags = 0;
+ origin_circuit_t *new_circ;
+ cpath_build_state_t *bstate;
+
+ tor_assert(circ);
+ /* This is initialized when allocating an origin circuit. */
+ tor_assert(circ->build_state);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
+
+ /* Ease our life. */
+ bstate = circ->build_state;
+
+ log_info(LD_REND, "Retrying rendezvous point circuit to %s",
+ safe_str_client(extend_info_describe(bstate->chosen_exit)));
+
+ /* Get the current build state flags for the next circuit. */
+ flags |= (bstate->need_uptime) ? CIRCLAUNCH_NEED_UPTIME : 0;
+ flags |= (bstate->need_capacity) ? CIRCLAUNCH_NEED_CAPACITY : 0;
+ flags |= (bstate->is_internal) ? CIRCLAUNCH_IS_INTERNAL : 0;
+
+ /* We do NOT add the onehop tunnel flag even though it might be a single
+ * onion service. The reason is that if we failed once to connect to the RP
+ * with a direct connection, we consider that chances are that we will fail
+ * again so try a 3-hop circuit and hope for the best. Because the service
+ * has no anonymity (single onion), this change of behavior won't affect
+ * security directly. */
+
+ new_circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND,
+ bstate->chosen_exit, flags);
+ if (new_circ == NULL) {
+ log_warn(LD_REND, "Failed to launch rendezvous circuit to %s",
+ safe_str_client(extend_info_describe(bstate->chosen_exit)));
+ goto done;
+ }
+
+ /* Transfer build state information to the new circuit state in part to
+ * catch any other failures. */
+ new_circ->build_state->failure_count = bstate->failure_count++;
+ new_circ->build_state->expiry_time = bstate->expiry_time;
+ new_circ->hs_ident = hs_ident_circuit_dup(circ->hs_ident);
+
+ done:
+ return;
+}
+
+/* Using an extend info object ei, set all possible link specifiers in lspecs.
+ * IPv4, legacy ID and ed25519 ID are mandatory thus MUST be present in ei. */
+static void
+get_lspecs_from_extend_info(const extend_info_t *ei, smartlist_t *lspecs)
+{
+ link_specifier_t *ls;
+
+ tor_assert(ei);
+ tor_assert(lspecs);
+
+ /* IPv4 is mandatory. */
+ ls = link_specifier_new();
+ link_specifier_set_ls_type(ls, LS_IPV4);
+ link_specifier_set_un_ipv4_addr(ls, tor_addr_to_ipv4h(&ei->addr));
+ link_specifier_set_un_ipv4_port(ls, ei->port);
+ /* Four bytes IPv4 and two bytes port. */
+ link_specifier_set_ls_len(ls, sizeof(ei->addr.addr.in_addr) +
+ sizeof(ei->port));
+ smartlist_add(lspecs, ls);
+
+ /* Legacy ID is mandatory. */
+ ls = link_specifier_new();
+ link_specifier_set_ls_type(ls, LS_LEGACY_ID);
+ memcpy(link_specifier_getarray_un_legacy_id(ls), ei->identity_digest,
+ link_specifier_getlen_un_legacy_id(ls));
+ link_specifier_set_ls_len(ls, link_specifier_getlen_un_legacy_id(ls));
+ smartlist_add(lspecs, ls);
+
+ /* ed25519 ID is mandatory. */
+ ls = link_specifier_new();
+ link_specifier_set_ls_type(ls, LS_ED25519_ID);
+ memcpy(link_specifier_getarray_un_ed25519_id(ls), &ei->ed_identity,
+ link_specifier_getlen_un_ed25519_id(ls));
+ link_specifier_set_ls_len(ls, link_specifier_getlen_un_ed25519_id(ls));
+ smartlist_add(lspecs, ls);
+
+ /* XXX: IPv6 is not clearly a thing in extend_info_t? */
+}
+
+/* Using the given descriptor intro point ip, the extend information of the
+ * rendezvous point rp_ei and the service's subcredential, populate the
+ * already allocated intro1_data object with the needed key material and link
+ * specifiers.
+ *
+ * This can't fail but the ip MUST be a valid object containing the needed
+ * keys and authentication method. */
+static void
+setup_introduce1_data(const hs_desc_intro_point_t *ip,
+ const extend_info_t *rp_ei,
+ const uint8_t *subcredential,
+ hs_cell_introduce1_data_t *intro1_data)
+{
+ smartlist_t *rp_lspecs;
+
+ tor_assert(ip);
+ tor_assert(rp_ei);
+ tor_assert(subcredential);
+ tor_assert(intro1_data);
+
+ /* Build the link specifiers from the extend information of the rendezvous
+ * circuit that we've picked previously. */
+ rp_lspecs = smartlist_new();
+ get_lspecs_from_extend_info(rp_ei, rp_lspecs);
+
+ /* Populate the introduce1 data object. */
+ memset(intro1_data, 0, sizeof(hs_cell_introduce1_data_t));
+ if (ip->legacy.key != NULL) {
+ intro1_data->is_legacy = 1;
+ intro1_data->legacy_key = ip->legacy.key;
+ }
+ intro1_data->auth_pk = &ip->auth_key_cert->signed_key;
+ intro1_data->enc_pk = &ip->enc_key;
+ intro1_data->subcredential = subcredential;
+ intro1_data->onion_pk = &rp_ei->curve25519_onion_key;
+ intro1_data->link_specifiers = rp_lspecs;
+}
+
+/* ========== */
+/* Public API */
+/* ========== */
+
+/* Return an introduction point circuit matching the given intro point object.
+ * NULL is returned is no such circuit can be found. */
+origin_circuit_t *
+hs_circ_service_get_intro_circ(const hs_service_intro_point_t *ip)
+{
+ origin_circuit_t *circ = NULL;
+
+ tor_assert(ip);
+
+ if (ip->base.is_only_legacy) {
+ uint8_t digest[DIGEST_LEN];
+ if (BUG(crypto_pk_get_digest(ip->legacy_key, (char *) digest) < 0)) {
+ goto end;
+ }
+ circ = hs_circuitmap_get_intro_circ_v2_service_side(digest);
+ } else {
+ circ = hs_circuitmap_get_intro_circ_v3_service_side(
+ &ip->auth_key_kp.pubkey);
+ }
+ end:
+ return circ;
+}
+
+/* Called when we fail building a rendezvous circuit at some point other than
+ * the last hop: launches a new circuit to the same rendezvous point. This
+ * supports legacy service.
+ *
+ * We currently relaunch connections to rendezvous points if:
+ * - A rendezvous circuit timed out before connecting to RP.
+ * - The redenzvous circuit failed to connect to the RP.
+ *
+ * We avoid relaunching a connection to this rendezvous point if:
+ * - We have already tried MAX_REND_FAILURES times to connect to this RP.
+ * - We've been trying to connect to this RP for more than MAX_REND_TIMEOUT
+ * seconds
+ * - We've already retried this specific rendezvous circuit.
+ */
+void
+hs_circ_retry_service_rendezvous_point(origin_circuit_t *circ)
+{
+ tor_assert(circ);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
+
+ /* Check if we are allowed to relaunch to the rendezvous point of circ. */
+ if (!can_relaunch_service_rendezvous_point(circ)) {
+ goto done;
+ }
+
+ /* Flag the circuit that we are relaunching so to avoid to relaunch twice a
+ * circuit to the same rendezvous point at the same time. */
+ circ->hs_service_side_rend_circ_has_been_relaunched = 1;
+
+ /* Legacy service don't have an hidden service ident. */
+ if (circ->hs_ident) {
+ retry_service_rendezvous_point(circ);
+ } else {
+ rend_service_relaunch_rendezvous(circ);
+ }
+
+ done:
+ return;
+}
+
+/* For a given service and a service intro point, launch a circuit to the
+ * extend info ei. If the service is a single onion, a one-hop circuit will be
+ * requested. Return 0 if the circuit was successfully launched and tagged
+ * with the correct identifier. On error, a negative value is returned. */
+int
+hs_circ_launch_intro_point(hs_service_t *service,
+ const hs_service_intro_point_t *ip,
+ extend_info_t *ei)
+{
+ /* Standard flags for introduction circuit. */
+ int ret = -1, circ_flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL;
+ origin_circuit_t *circ;
+
+ tor_assert(service);
+ tor_assert(ip);
+ tor_assert(ei);
+
+ /* Update circuit flags in case of a single onion service that requires a
+ * direct connection. */
+ if (service->config.is_single_onion) {
+ circ_flags |= CIRCLAUNCH_ONEHOP_TUNNEL;
+ }
+
+ log_info(LD_REND, "Launching a circuit to intro point %s for service %s.",
+ safe_str_client(extend_info_describe(ei)),
+ safe_str_client(service->onion_address));
+
+ /* Note down the launch for the retry period. Even if the circuit fails to
+ * be launched, we still want to respect the retry period to avoid stress on
+ * the circuit subsystem. */
+ service->state.num_intro_circ_launched++;
+ circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_ESTABLISH_INTRO,
+ ei, circ_flags);
+ if (circ == NULL) {
+ goto end;
+ }
+
+ /* Setup the circuit identifier and attach it to it. */
+ circ->hs_ident = create_intro_circuit_identifier(service, ip);
+ tor_assert(circ->hs_ident);
+ /* Register circuit in the global circuitmap. */
+ register_intro_circ(ip, circ);
+
+ /* Success. */
+ ret = 0;
+ end:
+ return ret;
+}
+
+/* Called when a service introduction point circuit is done building. Given
+ * the service and intro point object, this function will send the
+ * ESTABLISH_INTRO cell on the circuit. Return 0 on success. Return 1 if the
+ * circuit has been repurposed to General because we already have too many
+ * opened. */
+int
+hs_circ_service_intro_has_opened(hs_service_t *service,
+ hs_service_intro_point_t *ip,
+ const hs_service_descriptor_t *desc,
+ origin_circuit_t *circ)
+{
+ int ret = 0;
+ unsigned int num_intro_circ, num_needed_circ;
+
+ tor_assert(service);
+ tor_assert(ip);
+ tor_assert(desc);
+ tor_assert(circ);
+
+ /* Cound opened circuits that have sent ESTABLISH_INTRO cells or are already
+ * established introduction circuits */
+ num_intro_circ = count_opened_desc_intro_point_circuits(service, desc);
+ num_needed_circ = service->config.num_intro_points;
+ if (num_intro_circ > num_needed_circ) {
+ /* There are too many opened valid intro circuit for what the service
+ * needs so repurpose this one. */
+
+ /* XXX: Legacy code checks options->ExcludeNodes and if not NULL it just
+ * closes the circuit. I have NO idea why it does that so it hasn't been
+ * added here. I can only assume in case our ExcludeNodes list changes but
+ * in that case, all circuit are flagged unusable (config.c). --dgoulet */
+
+ log_info(LD_CIRC | LD_REND, "Introduction circuit just opened but we "
+ "have enough for service %s. Repurposing "
+ "it to general and leaving internal.",
+ safe_str_client(service->onion_address));
+ tor_assert(circ->build_state->is_internal);
+ /* Remove it from the circuitmap. */
+ hs_circuitmap_remove_circuit(TO_CIRCUIT(circ));
+ /* Cleaning up the hidden service identifier and repurpose. */
+ hs_ident_circuit_free(circ->hs_ident);
+ circ->hs_ident = NULL;
+ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_GENERAL);
+ /* Inform that this circuit just opened for this new purpose. */
+ circuit_has_opened(circ);
+ /* This return value indicate to the caller that the IP object should be
+ * removed from the service because it's corresponding circuit has just
+ * been repurposed. */
+ ret = 1;
+ goto done;
+ }
+
+ log_info(LD_REND, "Introduction circuit %u established for service %s.",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ circuit_log_path(LOG_INFO, LD_REND, circ);
+
+ /* Time to send an ESTABLISH_INTRO cell on this circuit. On error, this call
+ * makes sure the circuit gets closed. */
+ send_establish_intro(service, ip, circ);
+
+ done:
+ return ret;
+}
+
+/* Called when a service rendezvous point circuit is done building. Given the
+ * service and the circuit, this function will send a RENDEZVOUS1 cell on the
+ * circuit using the information in the circuit identifier. If the cell can't
+ * be sent, the circuit is closed. */
+void
+hs_circ_service_rp_has_opened(const hs_service_t *service,
+ origin_circuit_t *circ)
+{
+ size_t payload_len;
+ uint8_t payload[RELAY_PAYLOAD_SIZE] = {0};
+
+ tor_assert(service);
+ tor_assert(circ);
+ tor_assert(circ->hs_ident);
+
+ /* Some useful logging. */
+ log_info(LD_REND, "Rendezvous circuit %u has opened with cookie %s "
+ "for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ hex_str((const char *) circ->hs_ident->rendezvous_cookie,
+ REND_COOKIE_LEN),
+ safe_str_client(service->onion_address));
+ circuit_log_path(LOG_INFO, LD_REND, circ);
+
+ /* This can't fail. */
+ payload_len = hs_cell_build_rendezvous1(
+ circ->hs_ident->rendezvous_cookie,
+ sizeof(circ->hs_ident->rendezvous_cookie),
+ circ->hs_ident->rendezvous_handshake_info,
+ sizeof(circ->hs_ident->rendezvous_handshake_info),
+ payload);
+
+ if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ),
+ RELAY_COMMAND_RENDEZVOUS1,
+ (const char *) payload, payload_len,
+ circ->cpath->prev) < 0) {
+ /* On error, circuit is closed. */
+ log_warn(LD_REND, "Unable to send RENDEZVOUS1 cell on circuit %u "
+ "for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto done;
+ }
+
+ /* Setup end-to-end rendezvous circuit between the client and us. */
+ if (hs_circuit_setup_e2e_rend_circ(circ,
+ circ->hs_ident->rendezvous_ntor_key_seed,
+ sizeof(circ->hs_ident->rendezvous_ntor_key_seed),
+ 1) < 0) {
+ log_warn(LD_GENERAL, "Failed to setup circ");
+ goto done;
+ }
+
+ done:
+ memwipe(payload, 0, sizeof(payload));
+}
+
+/* Circ has been expecting an INTRO_ESTABLISHED cell that just arrived. Handle
+ * the INTRO_ESTABLISHED cell payload of length payload_len arriving on the
+ * given introduction circuit circ. The service is only used for logging
+ * purposes. Return 0 on success else a negative value. */
+int
+hs_circ_handle_intro_established(const hs_service_t *service,
+ const hs_service_intro_point_t *ip,
+ origin_circuit_t *circ,
+ const uint8_t *payload, size_t payload_len)
+{
+ int ret = -1;
+
+ tor_assert(service);
+ tor_assert(ip);
+ tor_assert(circ);
+ tor_assert(payload);
+
+ if (BUG(TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO)) {
+ goto done;
+ }
+
+ /* Try to parse the payload into a cell making sure we do actually have a
+ * valid cell. For a legacy node, it's an empty payload so as long as we
+ * have the cell, we are good. */
+ if (!ip->base.is_only_legacy &&
+ hs_cell_parse_intro_established(payload, payload_len) < 0) {
+ log_warn(LD_REND, "Unable to parse the INTRO_ESTABLISHED cell on "
+ "circuit %u for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto done;
+ }
+
+ /* Switch the purpose to a fully working intro point. */
+ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_S_INTRO);
+ /* Getting a valid INTRODUCE_ESTABLISHED means we've successfully used the
+ * circuit so update our pathbias subsystem. */
+ pathbias_mark_use_success(circ);
+ /* Success. */
+ ret = 0;
+
+ done:
+ return ret;
+}
+
+/* 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
+ * and the subcredential. Return 0 on success else a negative value. */
+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 uint8_t *payload, size_t payload_len)
+{
+ int ret = -1;
+ time_t elapsed;
+ hs_cell_introduce2_data_t data;
+
+ tor_assert(service);
+ tor_assert(circ);
+ tor_assert(ip);
+ tor_assert(subcredential);
+ tor_assert(payload);
+
+ /* Populate the data structure with everything we need for the cell to be
+ * 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 (hs_cell_parse_introduce2(&data, circ, service) < 0) {
+ goto done;
+ }
+
+ /* Check whether we've seen this REND_COOKIE before to detect repeats. */
+ if (replaycache_add_test_and_elapsed(
+ service->state.replay_cache_rend_cookie,
+ data.rendezvous_cookie, sizeof(data.rendezvous_cookie),
+ &elapsed)) {
+ /* A Tor client will send a new INTRODUCE1 cell with the same REND_COOKIE
+ * as its previous one if its intro circ times out while in state
+ * CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT. If we received the first
+ * INTRODUCE1 cell (the intro-point relay converts it into an INTRODUCE2
+ * cell), we are already trying to connect to that rend point (and may
+ * have already succeeded); drop this cell. */
+ log_info(LD_REND, "We received an INTRODUCE2 cell with same REND_COOKIE "
+ "field %ld seconds ago. Dropping cell.",
+ (long int) elapsed);
+ goto done;
+ }
+
+ /* At this point, we just confirmed that the full INTRODUCE2 cell is valid
+ * so increment our counter that we've seen one on this intro point. */
+ ip->introduce2_count++;
+
+ /* Launch rendezvous circuit with the onion key and rend cookie. */
+ launch_rendezvous_point_circuit(service, ip, &data);
+ /* Success. */
+ ret = 0;
+
+ done:
+ SMARTLIST_FOREACH(data.link_specifiers, link_specifier_t *, lspec,
+ link_specifier_free(lspec));
+ smartlist_free(data.link_specifiers);
+ memwipe(&data, 0, sizeof(data));
+ return ret;
+}
+
+/* Circuit <b>circ</b> just finished the rend ntor key exchange. Use the key
+ * exchange output material at <b>ntor_key_seed</b> and setup <b>circ</b> to
+ * serve as a rendezvous end-to-end circuit between the client and the
+ * service. If <b>is_service_side</b> is set, then we are the hidden service
+ * and the other side is the client.
+ *
+ * Return 0 if the operation went well; in case of error return -1. */
+int
+hs_circuit_setup_e2e_rend_circ(origin_circuit_t *circ,
+ const uint8_t *ntor_key_seed, size_t seed_len,
+ int is_service_side)
+{
+ if (BUG(!circuit_purpose_is_correct_for_rend(TO_CIRCUIT(circ)->purpose,
+ is_service_side))) {
+ return -1;
+ }
+
+ crypt_path_t *hop = create_rend_cpath(ntor_key_seed, seed_len,
+ is_service_side);
+ if (!hop) {
+ log_warn(LD_REND, "Couldn't get v3 %s cpath!",
+ is_service_side ? "service-side" : "client-side");
+ return -1;
+ }
+
+ finalize_rend_circuit(circ, hop, is_service_side);
+
+ return 0;
+}
+
+/* We are a v2 legacy HS client and we just received a RENDEZVOUS1 cell
+ * <b>rend_cell_body</b> on <b>circ</b>. Finish up the DH key exchange and then
+ * extend the crypt path of <b>circ</b> so that the hidden service is on the
+ * other side. */
+int
+hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ,
+ const uint8_t *rend_cell_body)
+{
+
+ if (BUG(!circuit_purpose_is_correct_for_rend(
+ TO_CIRCUIT(circ)->purpose, 0))) {
+ return -1;
+ }
+
+ crypt_path_t *hop = create_rend_cpath_legacy(circ, rend_cell_body);
+ if (!hop) {
+ log_warn(LD_GENERAL, "Couldn't get v2 cpath.");
+ return -1;
+ }
+
+ finalize_rend_circuit(circ, hop, 0);
+
+ return 0;
+}
+
+/* Given the introduction circuit intro_circ, the rendezvous circuit
+ * rend_circ, a descriptor intro point object ip and the service's
+ * subcredential, send an INTRODUCE1 cell on intro_circ.
+ *
+ * This will also setup the circuit identifier on rend_circ containing the key
+ * material for the handshake and e2e encryption. Return 0 on success else
+ * negative value. Because relay_send_command_from_edge() closes the circuit
+ * on error, it is possible that intro_circ is closed on error. */
+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)
+{
+ int ret = -1;
+ ssize_t payload_len;
+ uint8_t payload[RELAY_PAYLOAD_SIZE] = {0};
+ hs_cell_introduce1_data_t intro1_data;
+
+ tor_assert(intro_circ);
+ tor_assert(rend_circ);
+ tor_assert(ip);
+ tor_assert(subcredential);
+
+ /* This takes various objects in order to populate the introduce1 data
+ * object which is used to build the content of the cell. */
+ setup_introduce1_data(ip, rend_circ->build_state->chosen_exit,
+ subcredential, &intro1_data);
+
+ /* Final step before we encode a cell, we setup the circuit identifier which
+ * will generate both the rendezvous cookie and client keypair for this
+ * connection. Those are put in the ident. */
+ intro1_data.rendezvous_cookie = rend_circ->hs_ident->rendezvous_cookie;
+ intro1_data.client_kp = &rend_circ->hs_ident->rendezvous_client_kp;
+
+ memcpy(intro_circ->hs_ident->rendezvous_cookie,
+ rend_circ->hs_ident->rendezvous_cookie,
+ sizeof(intro_circ->hs_ident->rendezvous_cookie));
+
+ /* From the introduce1 data object, this will encode the INTRODUCE1 cell
+ * into payload which is then ready to be sent as is. */
+ payload_len = hs_cell_build_introduce1(&intro1_data, payload);
+ if (BUG(payload_len < 0)) {
+ goto done;
+ }
+
+ if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(intro_circ),
+ RELAY_COMMAND_INTRODUCE1,
+ (const char *) payload, payload_len,
+ intro_circ->cpath->prev) < 0) {
+ /* On error, circuit is closed. */
+ log_warn(LD_REND, "Unable to send INTRODUCE1 cell on circuit %u.",
+ TO_CIRCUIT(intro_circ)->n_circ_id);
+ goto done;
+ }
+
+ /* Success. */
+ ret = 0;
+ goto done;
+
+ done:
+ hs_cell_introduce1_data_clear(&intro1_data);
+ memwipe(payload, 0, sizeof(payload));
+ return ret;
+}
+
+/* Send an ESTABLISH_RENDEZVOUS cell along the rendezvous circuit circ. On
+ * success, 0 is returned else -1 and the circuit is marked for close. */
+int
+hs_circ_send_establish_rendezvous(origin_circuit_t *circ)
+{
+ ssize_t cell_len = 0;
+ uint8_t cell[RELAY_PAYLOAD_SIZE] = {0};
+
+ tor_assert(circ);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND);
+
+ log_info(LD_REND, "Send an ESTABLISH_RENDEZVOUS cell on circuit %u",
+ TO_CIRCUIT(circ)->n_circ_id);
+
+ /* Set timestamp_dirty, because circuit_expire_building expects it,
+ * and the rend cookie also means we've used the circ. */
+ TO_CIRCUIT(circ)->timestamp_dirty = time(NULL);
+
+ /* We've attempted to use this circuit. Probe it if we fail */
+ pathbias_count_use_attempt(circ);
+
+ /* Generate the RENDEZVOUS_COOKIE and place it in the identifier so we can
+ * complete the handshake when receiving the acknowledgement. */
+ crypto_rand((char *) circ->hs_ident->rendezvous_cookie, HS_REND_COOKIE_LEN);
+ /* Generate the client keypair. No need to be extra strong, not long term */
+ curve25519_keypair_generate(&circ->hs_ident->rendezvous_client_kp, 0);
+
+ cell_len =
+ hs_cell_build_establish_rendezvous(circ->hs_ident->rendezvous_cookie,
+ cell);
+ if (BUG(cell_len < 0)) {
+ goto err;
+ }
+
+ if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ),
+ RELAY_COMMAND_ESTABLISH_RENDEZVOUS,
+ (const char *) cell, cell_len,
+ circ->cpath->prev) < 0) {
+ /* Circuit has been marked for close */
+ log_warn(LD_REND, "Unable to send ESTABLISH_RENDEZVOUS cell on "
+ "circuit %u", TO_CIRCUIT(circ)->n_circ_id);
+ memwipe(cell, 0, cell_len);
+ goto err;
+ }
+
+ memwipe(cell, 0, cell_len);
+ return 0;
+ err:
+ return -1;
+}
+
diff --git a/src/or/hs_circuit.h b/src/or/hs_circuit.h
new file mode 100644
index 0000000000..3b0e3aca1c
--- /dev/null
+++ b/src/or/hs_circuit.h
@@ -0,0 +1,63 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_circuit.h
+ * \brief Header file containing circuit data for the whole HS subsytem.
+ **/
+
+#ifndef TOR_HS_CIRCUIT_H
+#define TOR_HS_CIRCUIT_H
+
+#include "or.h"
+#include "crypto.h"
+#include "crypto_ed25519.h"
+
+#include "hs_service.h"
+
+/* Circuit API. */
+int hs_circ_service_intro_has_opened(hs_service_t *service,
+ hs_service_intro_point_t *ip,
+ const hs_service_descriptor_t *desc,
+ origin_circuit_t *circ);
+void hs_circ_service_rp_has_opened(const hs_service_t *service,
+ origin_circuit_t *circ);
+int hs_circ_launch_intro_point(hs_service_t *service,
+ const hs_service_intro_point_t *ip,
+ extend_info_t *ei);
+int hs_circ_launch_rendezvous_point(const hs_service_t *service,
+ const curve25519_public_key_t *onion_key,
+ const uint8_t *rendezvous_cookie);
+void hs_circ_retry_service_rendezvous_point(origin_circuit_t *circ);
+
+origin_circuit_t *hs_circ_service_get_intro_circ(
+ const hs_service_intro_point_t *ip);
+
+/* Cell API. */
+int hs_circ_handle_intro_established(const hs_service_t *service,
+ const hs_service_intro_point_t *ip,
+ origin_circuit_t *circ,
+ const uint8_t *payload,
+ size_t payload_len);
+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 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);
+int hs_circ_send_establish_rendezvous(origin_circuit_t *circ);
+
+/* e2e circuit API. */
+
+int hs_circuit_setup_e2e_rend_circ(origin_circuit_t *circ,
+ const uint8_t *ntor_key_seed,
+ size_t seed_len,
+ int is_service_side);
+int hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ,
+ const uint8_t *rend_cell_body);
+
+#endif /* TOR_HS_CIRCUIT_H */
+
diff --git a/src/or/hs_circuitmap.c b/src/or/hs_circuitmap.c
index ea66fb5194..09704d796c 100644
--- a/src/or/hs_circuitmap.c
+++ b/src/or/hs_circuitmap.c
@@ -5,8 +5,10 @@
* \file hs_circuitmap.c
*
* \brief Hidden service circuitmap: A hash table that maps binary tokens to
- * introduction and rendezvous circuits; it's used both by relays acting as
- * intro points and rendezvous points, and also by hidden services themselves.
+ * introduction and rendezvous circuits; it's used:
+ * (a) by relays acting as intro points and rendezvous points
+ * (b) by hidden services to find intro and rend circuits and
+ * (c) by HS clients to find rendezvous circuits.
**/
#define HS_CIRCUITMAP_PRIVATE
@@ -404,6 +406,37 @@ hs_circuitmap_get_rend_circ_service_side(const uint8_t *cookie)
return circ;
}
+/* Public function: Return client-side rendezvous circuit with rendezvous
+ * <b>cookie</b>. It will first lookup for the CIRCUIT_PURPOSE_C_REND_READY
+ * purpose and then try for CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED and then
+ * finally tries for CIRCUIT_PURPOSE_C_ESTABLISH_REND.
+ *
+ * Return NULL if no such circuit is found in the circuitmap. */
+origin_circuit_t *
+hs_circuitmap_get_rend_circ_client_side(const uint8_t *cookie)
+{
+ origin_circuit_t *circ = NULL;
+
+ circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE,
+ REND_TOKEN_LEN, cookie,
+ CIRCUIT_PURPOSE_C_REND_READY);
+ if (circ) {
+ return circ;
+ }
+
+ circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE,
+ REND_TOKEN_LEN, cookie,
+ CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED);
+ if (circ) {
+ return circ;
+ }
+
+ circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE,
+ REND_TOKEN_LEN, cookie,
+ CIRCUIT_PURPOSE_C_ESTABLISH_REND);
+ return circ;
+}
+
/**** Public servide-side setters: */
/* Public function: Register v2 intro circuit with key <b>digest</b> to the
@@ -439,6 +472,21 @@ hs_circuitmap_register_rend_circ_service_side(origin_circuit_t *circ,
REND_TOKEN_LEN, cookie);
}
+/* Public function: Register rendezvous circuit with key <b>cookie</b> to the
+ * client-side circuitmap. */
+void
+hs_circuitmap_register_rend_circ_client_side(origin_circuit_t *or_circ,
+ const uint8_t *cookie)
+{
+ circuit_t *circ = TO_CIRCUIT(or_circ);
+ { /* Basic circ purpose sanity checking */
+ tor_assert_nonfatal(circ->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND);
+ }
+
+ hs_circuitmap_register_circuit(circ, HS_TOKEN_REND_CLIENT_SIDE,
+ REND_TOKEN_LEN, cookie);
+}
+
/**** Misc public functions: */
/** Public function: Remove this circuit from the HS circuitmap. Clear its HS
diff --git a/src/or/hs_circuitmap.h b/src/or/hs_circuitmap.h
index 33d5b64117..0903de2347 100644
--- a/src/or/hs_circuitmap.h
+++ b/src/or/hs_circuitmap.h
@@ -43,6 +43,8 @@ struct origin_circuit_t *
hs_circuitmap_get_intro_circ_v2_service_side(const uint8_t *digest);
struct origin_circuit_t *
hs_circuitmap_get_rend_circ_service_side(const uint8_t *cookie);
+struct origin_circuit_t *
+hs_circuitmap_get_rend_circ_client_side(const uint8_t *cookie);
void hs_circuitmap_register_intro_circ_v2_service_side(
struct origin_circuit_t *circ,
@@ -53,6 +55,9 @@ void hs_circuitmap_register_intro_circ_v3_service_side(
void hs_circuitmap_register_rend_circ_service_side(
struct origin_circuit_t *circ,
const uint8_t *cookie);
+void hs_circuitmap_register_rend_circ_client_side(
+ struct origin_circuit_t *circ,
+ const uint8_t *cookie);
void hs_circuitmap_remove_circuit(struct circuit_t *circ);
@@ -76,6 +81,9 @@ typedef enum {
HS_TOKEN_INTRO_V2_SERVICE_SIDE,
/** A v3 introduction point pubkey on a hidden service (256bit) */
HS_TOKEN_INTRO_V3_SERVICE_SIDE,
+
+ /** A rendezvous cookie on the client side (128bit) */
+ HS_TOKEN_REND_CLIENT_SIDE,
} hs_token_type_t;
/** Represents a token used in the HS protocol. Each such token maps to a
diff --git a/src/or/hs_client.c b/src/or/hs_client.c
new file mode 100644
index 0000000000..99be058eb7
--- /dev/null
+++ b/src/or/hs_client.c
@@ -0,0 +1,1253 @@
+/* Copyright (c) 2016-2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_client.c
+ * \brief Implement next generation hidden service client functionality
+ **/
+
+#include "or.h"
+#include "hs_circuit.h"
+#include "hs_ident.h"
+#include "connection_edge.h"
+#include "container.h"
+#include "rendclient.h"
+#include "hs_descriptor.h"
+#include "hs_cache.h"
+#include "hs_cell.h"
+#include "hs_ident.h"
+#include "config.h"
+#include "directory.h"
+#include "hs_client.h"
+#include "router.h"
+#include "routerset.h"
+#include "circuitlist.h"
+#include "circuituse.h"
+#include "connection.h"
+#include "circpathbias.h"
+#include "connection.h"
+#include "hs_ntor.h"
+#include "circuitbuild.h"
+
+/* Get all connections that are waiting on a circuit and flag them back to
+ * waiting for a hidden service descriptor for the given service key
+ * service_identity_pk. */
+static void
+flag_all_conn_wait_desc(const ed25519_public_key_t *service_identity_pk)
+{
+ tor_assert(service_identity_pk);
+
+ smartlist_t *conns =
+ connection_list_by_type_state(CONN_TYPE_AP, AP_CONN_STATE_CIRCUIT_WAIT);
+
+ SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) {
+ edge_connection_t *edge_conn;
+ if (BUG(!CONN_IS_EDGE(conn))) {
+ continue;
+ }
+ edge_conn = TO_EDGE_CONN(conn);
+ if (edge_conn->hs_ident &&
+ ed25519_pubkey_eq(&edge_conn->hs_ident->identity_pk,
+ service_identity_pk)) {
+ connection_ap_mark_as_non_pending_circuit(TO_ENTRY_CONN(conn));
+ conn->state = AP_CONN_STATE_RENDDESC_WAIT;
+ }
+ } SMARTLIST_FOREACH_END(conn);
+
+ smartlist_free(conns);
+}
+
+/* Remove tracked HSDir requests from our history for this hidden service
+ * identity public key. */
+static void
+purge_hid_serv_request(const ed25519_public_key_t *identity_pk)
+{
+ char base64_blinded_pk[ED25519_BASE64_LEN + 1];
+ ed25519_public_key_t blinded_pk;
+
+ tor_assert(identity_pk);
+
+ /* Get blinded pubkey of hidden service. It is possible that we just moved
+ * to a new time period meaning that we won't be able to purge the request
+ * from the previous time period. That is fine because they will expire at
+ * some point and we don't care about those anymore. */
+ hs_build_blinded_pubkey(identity_pk, NULL, 0,
+ hs_get_time_period_num(approx_time()), &blinded_pk);
+ if (BUG(ed25519_public_to_base64(base64_blinded_pk, &blinded_pk) < 0)) {
+ return;
+ }
+ /* Purge last hidden service request from cache for this blinded key. */
+ hs_purge_hid_serv_from_last_hid_serv_requests(base64_blinded_pk);
+}
+
+/* A v3 HS circuit successfully connected to the hidden service. Update the
+ * stream state at <b>hs_conn_ident</b> appropriately. */
+static void
+note_connection_attempt_succeeded(const hs_ident_edge_conn_t *hs_conn_ident)
+{
+ tor_assert(hs_conn_ident);
+
+ /* Remove from the hid serv cache all requests for that service so we can
+ * query the HSDir again later on for various reasons. */
+ purge_hid_serv_request(&hs_conn_ident->identity_pk);
+
+ /* The v2 subsystem cleans up the intro point time out flag at this stage.
+ * We don't try to do it here because we still need to keep intact the intro
+ * point state for future connections. Even though we are able to connect to
+ * the service, doesn't mean we should reset the timed out intro points.
+ *
+ * It is not possible to have successfully connected to an intro point
+ * present in our cache that was on error or timed out. Every entry in that
+ * cache have a 2 minutes lifetime so ultimately the intro point(s) state
+ * will be reset and thus possible to be retried. */
+}
+
+/* Given the pubkey of a hidden service in <b>onion_identity_pk</b>, fetch its
+ * descriptor by launching a dir connection to <b>hsdir</b>. Return 1 on
+ * success or -1 on error. */
+static int
+directory_launch_v3_desc_fetch(const ed25519_public_key_t *onion_identity_pk,
+ const routerstatus_t *hsdir)
+{
+ uint64_t current_time_period = hs_get_time_period_num(approx_time());
+ ed25519_public_key_t blinded_pubkey;
+ char base64_blinded_pubkey[ED25519_BASE64_LEN + 1];
+ hs_ident_dir_conn_t hs_conn_dir_ident;
+ int retval;
+
+ tor_assert(hsdir);
+ tor_assert(onion_identity_pk);
+
+ /* Get blinded pubkey */
+ hs_build_blinded_pubkey(onion_identity_pk, NULL, 0,
+ current_time_period, &blinded_pubkey);
+ /* ...and base64 it. */
+ retval = ed25519_public_to_base64(base64_blinded_pubkey, &blinded_pubkey);
+ if (BUG(retval < 0)) {
+ return -1;
+ }
+
+ /* Copy onion pk to a dir_ident so that we attach it to the dir conn */
+ ed25519_pubkey_copy(&hs_conn_dir_ident.identity_pk, onion_identity_pk);
+
+ /* Setup directory request */
+ directory_request_t *req =
+ directory_request_new(DIR_PURPOSE_FETCH_HSDESC);
+ directory_request_set_routerstatus(req, hsdir);
+ directory_request_set_indirection(req, DIRIND_ANONYMOUS);
+ directory_request_set_resource(req, base64_blinded_pubkey);
+ directory_request_fetch_set_hs_ident(req, &hs_conn_dir_ident);
+ directory_initiate_request(req);
+ directory_request_free(req);
+
+ log_info(LD_REND, "Descriptor fetch request for service %s with blinded "
+ "key %s to directory %s",
+ safe_str_client(ed25519_fmt(onion_identity_pk)),
+ safe_str_client(base64_blinded_pubkey),
+ safe_str_client(routerstatus_describe(hsdir)));
+
+ /* Cleanup memory. */
+ memwipe(&blinded_pubkey, 0, sizeof(blinded_pubkey));
+ memwipe(base64_blinded_pubkey, 0, sizeof(base64_blinded_pubkey));
+ memwipe(&hs_conn_dir_ident, 0, sizeof(hs_conn_dir_ident));
+
+ return 1;
+}
+
+/** Return the HSDir we should use to fetch the descriptor of the hidden
+ * service with identity key <b>onion_identity_pk</b>. */
+static routerstatus_t *
+pick_hsdir_v3(const ed25519_public_key_t *onion_identity_pk)
+{
+ int retval;
+ char base64_blinded_pubkey[ED25519_BASE64_LEN + 1];
+ uint64_t current_time_period = hs_get_time_period_num(approx_time());
+ smartlist_t *responsible_hsdirs;
+ ed25519_public_key_t blinded_pubkey;
+ routerstatus_t *hsdir_rs = NULL;
+
+ tor_assert(onion_identity_pk);
+
+ responsible_hsdirs = smartlist_new();
+
+ /* Get blinded pubkey of hidden service */
+ hs_build_blinded_pubkey(onion_identity_pk, NULL, 0,
+ current_time_period, &blinded_pubkey);
+ /* ...and base64 it. */
+ retval = ed25519_public_to_base64(base64_blinded_pubkey, &blinded_pubkey);
+ if (BUG(retval < 0)) {
+ return NULL;
+ }
+
+ /* Get responsible hsdirs of service for this time period */
+ hs_get_responsible_hsdirs(&blinded_pubkey, current_time_period, 0, 1,
+ responsible_hsdirs);
+
+ log_debug(LD_REND, "Found %d responsible HSDirs and about to pick one.",
+ smartlist_len(responsible_hsdirs));
+
+ /* Pick an HSDir from the responsible ones. The ownership of
+ * responsible_hsdirs is given to this function so no need to free it. */
+ hsdir_rs = hs_pick_hsdir(responsible_hsdirs, base64_blinded_pubkey);
+
+ return hsdir_rs;
+}
+
+/** Fetch a v3 descriptor using the given <b>onion_identity_pk</b>.
+ *
+ * On success, 1 is returned. If no hidden service is left to ask, return 0.
+ * On error, -1 is returned. */
+static int
+fetch_v3_desc(const ed25519_public_key_t *onion_identity_pk)
+{
+ routerstatus_t *hsdir_rs =NULL;
+
+ tor_assert(onion_identity_pk);
+
+ hsdir_rs = pick_hsdir_v3(onion_identity_pk);
+ if (!hsdir_rs) {
+ log_info(LD_REND, "Couldn't pick a v3 hsdir.");
+ return 0;
+ }
+
+ return directory_launch_v3_desc_fetch(onion_identity_pk, hsdir_rs);
+}
+
+/* Make sure that the given v3 origin circuit circ is a valid correct
+ * introduction circuit. This will BUG() on any problems and hard assert if
+ * the anonymity of the circuit is not ok. Return 0 on success else -1 where
+ * the circuit should be mark for closed immediately. */
+static int
+intro_circ_is_ok(const origin_circuit_t *circ)
+{
+ int ret = 0;
+
+ tor_assert(circ);
+
+ if (BUG(TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCING &&
+ TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT &&
+ TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACKED)) {
+ ret = -1;
+ }
+ if (BUG(circ->hs_ident == NULL)) {
+ ret = -1;
+ }
+ if (BUG(!hs_ident_intro_circ_is_valid(circ->hs_ident))) {
+ ret = -1;
+ }
+
+ /* This can stop the tor daemon but we want that since if we don't have
+ * anonymity on this circuit, something went really wrong. */
+ assert_circ_anonymity_ok(circ, get_options());
+ return ret;
+}
+
+/* Find a descriptor intro point object that matches the given ident in the
+ * given descriptor desc. Return NULL if not found. */
+static const hs_desc_intro_point_t *
+find_desc_intro_point_by_ident(const hs_ident_circuit_t *ident,
+ const hs_descriptor_t *desc)
+{
+ const hs_desc_intro_point_t *intro_point = NULL;
+
+ tor_assert(ident);
+ tor_assert(desc);
+
+ SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points,
+ const hs_desc_intro_point_t *, ip) {
+ if (ed25519_pubkey_eq(&ident->intro_auth_pk,
+ &ip->auth_key_cert->signed_key)) {
+ intro_point = ip;
+ break;
+ }
+ } SMARTLIST_FOREACH_END(ip);
+
+ return intro_point;
+}
+
+/* Find a descriptor intro point object from the descriptor object desc that
+ * matches the given legacy identity digest in legacy_id. Return NULL if not
+ * found. */
+static hs_desc_intro_point_t *
+find_desc_intro_point_by_legacy_id(const char *legacy_id,
+ const hs_descriptor_t *desc)
+{
+ hs_desc_intro_point_t *ret_ip = NULL;
+
+ tor_assert(legacy_id);
+ tor_assert(desc);
+
+ /* We will go over every intro point and try to find which one is linked to
+ * that circuit. Those lists are small so it's not that expensive. */
+ SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points,
+ hs_desc_intro_point_t *, ip) {
+ SMARTLIST_FOREACH_BEGIN(ip->link_specifiers,
+ const hs_desc_link_specifier_t *, lspec) {
+ /* Not all tor node have an ed25519 identity key so we still rely on the
+ * legacy identity digest. */
+ if (lspec->type != LS_LEGACY_ID) {
+ continue;
+ }
+ if (fast_memneq(legacy_id, lspec->u.legacy_id, DIGEST_LEN)) {
+ break;
+ }
+ /* Found it. */
+ ret_ip = ip;
+ goto end;
+ } SMARTLIST_FOREACH_END(lspec);
+ } SMARTLIST_FOREACH_END(ip);
+
+ end:
+ return ret_ip;
+}
+
+/* Send an INTRODUCE1 cell along the intro circuit and populate the rend
+ * circuit identifier with the needed key material for the e2e encryption.
+ * Return 0 on success, -1 if there is a transient error such that an action
+ * has been taken to recover and -2 if there is a permanent error indicating
+ * that both circuits were closed. */
+static int
+send_introduce1(origin_circuit_t *intro_circ,
+ origin_circuit_t *rend_circ)
+{
+ int status;
+ char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1];
+ const ed25519_public_key_t *service_identity_pk = NULL;
+ const hs_desc_intro_point_t *ip;
+
+ tor_assert(rend_circ);
+ if (intro_circ_is_ok(intro_circ) < 0) {
+ goto perm_err;
+ }
+
+ service_identity_pk = &intro_circ->hs_ident->identity_pk;
+ /* For logging purposes. There will be a time where the hs_ident will have a
+ * version number but for now there is none because it's all v3. */
+ hs_build_address(service_identity_pk, HS_VERSION_THREE, onion_address);
+
+ log_info(LD_REND, "Sending INTRODUCE1 cell to service %s on circuit %u",
+ safe_str_client(onion_address), TO_CIRCUIT(intro_circ)->n_circ_id);
+
+ /* 1) Get descriptor from our cache. */
+ const hs_descriptor_t *desc =
+ hs_cache_lookup_as_client(service_identity_pk);
+ if (desc == NULL || !hs_client_any_intro_points_usable(service_identity_pk,
+ desc)) {
+ log_info(LD_REND, "Request to %s %s. Trying to fetch a new descriptor.",
+ safe_str_client(onion_address),
+ (desc) ? "didn't have usable intro points" :
+ "didn't have a descriptor");
+ hs_client_refetch_hsdesc(service_identity_pk);
+ /* We just triggered a refetch, make sure every connections are back
+ * waiting for that descriptor. */
+ flag_all_conn_wait_desc(service_identity_pk);
+ /* We just asked for a refetch so this is a transient error. */
+ goto tran_err;
+ }
+
+ /* We need to find which intro point in the descriptor we are connected to
+ * on intro_circ. */
+ ip = find_desc_intro_point_by_ident(intro_circ->hs_ident, desc);
+ if (BUG(ip == NULL)) {
+ /* If we can find a descriptor from this introduction circuit ident, we
+ * must have a valid intro point object. Permanent error. */
+ goto perm_err;
+ }
+
+ /* Send the INTRODUCE1 cell. */
+ if (hs_circ_send_introduce1(intro_circ, rend_circ, ip,
+ desc->subcredential) < 0) {
+ /* Unable to send the cell, the intro circuit has been marked for close so
+ * this is a permanent error. */
+ tor_assert_nonfatal(TO_CIRCUIT(intro_circ)->marked_for_close);
+ goto perm_err;
+ }
+
+ /* Cell has been sent successfully. Copy the introduction point
+ * authentication and encryption key in the rendezvous circuit identifier so
+ * we can compute the ntor keys when we receive the RENDEZVOUS2 cell. */
+ memcpy(&rend_circ->hs_ident->intro_enc_pk, &ip->enc_key,
+ sizeof(rend_circ->hs_ident->intro_enc_pk));
+ ed25519_pubkey_copy(&rend_circ->hs_ident->intro_auth_pk,
+ &intro_circ->hs_ident->intro_auth_pk);
+
+ /* Now, we wait for an ACK or NAK on this circuit. */
+ circuit_change_purpose(TO_CIRCUIT(intro_circ),
+ CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT);
+ /* Set timestamp_dirty, because circuit_expire_building expects it to
+ * specify when a circuit entered the _C_INTRODUCE_ACK_WAIT state. */
+ TO_CIRCUIT(intro_circ)->timestamp_dirty = time(NULL);
+ pathbias_count_use_attempt(intro_circ);
+
+ /* Success. */
+ status = 0;
+ goto end;
+
+ perm_err:
+ /* Permanent error: it is possible that the intro circuit was closed prior
+ * because we weren't able to send the cell. Make sure we don't double close
+ * it which would result in a warning. */
+ if (!TO_CIRCUIT(intro_circ)->marked_for_close) {
+ circuit_mark_for_close(TO_CIRCUIT(intro_circ), END_CIRC_REASON_INTERNAL);
+ }
+ circuit_mark_for_close(TO_CIRCUIT(rend_circ), END_CIRC_REASON_INTERNAL);
+ status = -2;
+ goto end;
+
+ tran_err:
+ status = -1;
+
+ end:
+ memwipe(onion_address, 0, sizeof(onion_address));
+ return status;
+}
+
+/* Using the introduction circuit circ, setup the authentication key of the
+ * intro point this circuit has extended to. */
+static void
+setup_intro_circ_auth_key(origin_circuit_t *circ)
+{
+ const hs_descriptor_t *desc;
+ const hs_desc_intro_point_t *ip;
+
+ tor_assert(circ);
+
+ desc = hs_cache_lookup_as_client(&circ->hs_ident->identity_pk);
+ if (BUG(desc == NULL)) {
+ /* Opening intro circuit without the descriptor is no good... */
+ goto end;
+ }
+
+ /* We will go over every intro point and try to find which one is linked to
+ * that circuit. Those lists are small so it's not that expensive. */
+ ip = find_desc_intro_point_by_legacy_id(
+ circ->build_state->chosen_exit->identity_digest, desc);
+ if (ip) {
+ /* We got it, copy its authentication key to the identifier. */
+ ed25519_pubkey_copy(&circ->hs_ident->intro_auth_pk,
+ &ip->auth_key_cert->signed_key);
+ goto end;
+ }
+
+ /* Reaching this point means we didn't find any intro point for this circuit
+ * which is not suppose to happen. */
+ tor_assert_nonfatal_unreached();
+
+ end:
+ return;
+}
+
+/* Called when an introduction circuit has opened. */
+static void
+client_intro_circ_has_opened(origin_circuit_t *circ)
+{
+ tor_assert(circ);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_C_INTRODUCING);
+ log_info(LD_REND, "Introduction circuit %u has opened. Attaching streams.",
+ (unsigned int) TO_CIRCUIT(circ)->n_circ_id);
+
+ /* This is an introduction circuit so we'll attach the correct
+ * authentication key to the circuit identifier so it can be identified
+ * properly later on. */
+ setup_intro_circ_auth_key(circ);
+
+ connection_ap_attach_pending(1);
+}
+
+/* Called when a rendezvous circuit has opened. */
+static void
+client_rendezvous_circ_has_opened(origin_circuit_t *circ)
+{
+ tor_assert(circ);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND);
+
+ log_info(LD_REND, "Rendezvous circuit has opened to %s.",
+ safe_str_client(
+ extend_info_describe(circ->build_state->chosen_exit)));
+
+ /* Ignore returned value, nothing we can really do. On failure, the circuit
+ * will be marked for close. */
+ hs_circ_send_establish_rendezvous(circ);
+
+ /* Register rend circuit in circuitmap if it's still alive. */
+ if (!TO_CIRCUIT(circ)->marked_for_close) {
+ hs_circuitmap_register_rend_circ_client_side(circ,
+ circ->hs_ident->rendezvous_cookie);
+ }
+}
+
+/* This is an helper function that convert a descriptor intro point object ip
+ * to a newly allocated extend_info_t object fully initialized. Return NULL if
+ * we can't convert it for which chances are that we are missing or malformed
+ * link specifiers. */
+static extend_info_t *
+desc_intro_point_to_extend_info(const hs_desc_intro_point_t *ip)
+{
+ extend_info_t *ei;
+ smartlist_t *lspecs = smartlist_new();
+
+ tor_assert(ip);
+
+ /* We first encode the descriptor link specifiers into the binary
+ * representation which is a trunnel object. */
+ SMARTLIST_FOREACH_BEGIN(ip->link_specifiers,
+ const hs_desc_link_specifier_t *, desc_lspec) {
+ link_specifier_t *lspec = hs_desc_lspec_to_trunnel(desc_lspec);
+ smartlist_add(lspecs, lspec);
+ } SMARTLIST_FOREACH_END(desc_lspec);
+
+ /* Explicitely put the direct connection option to 0 because this is client
+ * side and there is no such thing as a non anonymous client. */
+ ei = hs_get_extend_info_from_lspecs(lspecs, &ip->onion_key, 0);
+
+ SMARTLIST_FOREACH(lspecs, link_specifier_t *, ls, link_specifier_free(ls));
+ smartlist_free(lspecs);
+ return ei;
+}
+
+/* Return true iff the intro point ip for the service service_pk is usable.
+ * This function checks if the intro point is in the client intro state cache
+ * and checks at the failures. It is considered usable if:
+ * - No error happened (INTRO_POINT_FAILURE_GENERIC)
+ * - It is not flagged as timed out (INTRO_POINT_FAILURE_TIMEOUT)
+ * - The unreachable count is lower than
+ * MAX_INTRO_POINT_REACHABILITY_FAILURES (INTRO_POINT_FAILURE_UNREACHABLE)
+ */
+static int
+intro_point_is_usable(const ed25519_public_key_t *service_pk,
+ const hs_desc_intro_point_t *ip)
+{
+ const hs_cache_intro_state_t *state;
+
+ tor_assert(service_pk);
+ tor_assert(ip);
+
+ state = hs_cache_client_intro_state_find(service_pk,
+ &ip->auth_key_cert->signed_key);
+ if (state == NULL) {
+ /* This means we've never encountered any problem thus usable. */
+ goto usable;
+ }
+ if (state->error) {
+ log_info(LD_REND, "Intro point with auth key %s had an error. Not usable",
+ safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key)));
+ goto not_usable;
+ }
+ if (state->timed_out) {
+ log_info(LD_REND, "Intro point with auth key %s timed out. Not usable",
+ safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key)));
+ goto not_usable;
+ }
+ if (state->unreachable_count >= MAX_INTRO_POINT_REACHABILITY_FAILURES) {
+ log_info(LD_REND, "Intro point with auth key %s unreachable. Not usable",
+ safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key)));
+ goto not_usable;
+ }
+
+ usable:
+ return 1;
+ not_usable:
+ return 0;
+}
+
+/* Using a descriptor desc, return a newly allocated extend_info_t object of a
+ * randomly picked introduction point from its list. Return NULL if none are
+ * usable. */
+static extend_info_t *
+client_get_random_intro(const ed25519_public_key_t *service_pk)
+{
+ extend_info_t *ei = NULL, *ei_excluded = NULL;
+ smartlist_t *usable_ips = NULL;
+ const hs_descriptor_t *desc;
+ const hs_desc_encrypted_data_t *enc_data;
+ const or_options_t *options = get_options();
+
+ tor_assert(service_pk);
+
+ desc = hs_cache_lookup_as_client(service_pk);
+ if (desc == NULL || !hs_client_any_intro_points_usable(service_pk,
+ desc)) {
+ log_info(LD_REND, "Unable to randomly select an introduction point "
+ "because descriptor %s.",
+ (desc) ? "doesn't have usable intro point" : "is missing");
+ goto end;
+ }
+
+ enc_data = &desc->encrypted_data;
+ usable_ips = smartlist_new();
+ smartlist_add_all(usable_ips, enc_data->intro_points);
+ while (smartlist_len(usable_ips) != 0) {
+ int idx;
+ const hs_desc_intro_point_t *ip;
+
+ /* Pick a random intro point and immediately remove it from the usable
+ * list so we don't pick it again if we have to iterate more. */
+ idx = crypto_rand_int(smartlist_len(usable_ips));
+ ip = smartlist_get(usable_ips, idx);
+ smartlist_del(usable_ips, idx);
+
+ /* We need to make sure we have a usable intro points which is in a good
+ * state in our cache. */
+ if (!intro_point_is_usable(service_pk, ip)) {
+ continue;
+ }
+
+ /* Generate an extend info object from the intro point object. */
+ ei = desc_intro_point_to_extend_info(ip);
+ if (ei == NULL) {
+ /* We can get here for instance if the intro point is a private address
+ * and we aren't allowed to extend to those. */
+ continue;
+ }
+
+ /* Test the pick against ExcludeNodes. */
+ if (routerset_contains_extendinfo(options->ExcludeNodes, ei)) {
+ /* If this pick is in the ExcludeNodes list, we keep its reference so if
+ * we ever end up not being able to pick anything else and StrictNodes is
+ * unset, we'll use it. */
+ ei_excluded = ei;
+ continue;
+ }
+
+ /* Good pick! Let's go with this. */
+ goto end;
+ }
+
+ /* Reaching this point means a couple of things. Either we can't use any of
+ * the intro point listed because the IP address can't be extended to or it
+ * is listed in the ExcludeNodes list. In the later case, if StrictNodes is
+ * set, we are forced to not use anything. */
+ ei = ei_excluded;
+ if (options->StrictNodes) {
+ log_warn(LD_REND, "Every introduction points are in the ExcludeNodes set "
+ "and StrictNodes is set. We can't connect.");
+ ei = NULL;
+ }
+
+ end:
+ smartlist_free(usable_ips);
+ return ei;
+}
+
+/* For this introduction circuit, we'll look at if we have any usable
+ * introduction point left for this service. If so, we'll use the circuit to
+ * re-extend to a new intro point. Else, we'll close the circuit and its
+ * corresponding rendezvous circuit. Return 0 if we are re-extending else -1
+ * if we are closing the circuits.
+ *
+ * This is called when getting an INTRODUCE_ACK cell with a NACK. */
+static int
+close_or_reextend_intro_circ(origin_circuit_t *intro_circ)
+{
+ int ret = -1;
+ const hs_descriptor_t *desc;
+ origin_circuit_t *rend_circ;
+
+ tor_assert(intro_circ);
+
+ desc = hs_cache_lookup_as_client(&intro_circ->hs_ident->identity_pk);
+ if (BUG(desc == NULL)) {
+ /* We can't continue without a descriptor. */
+ goto close;
+ }
+ /* We still have the descriptor, great! Let's try to see if we can
+ * re-extend by looking up if there are any usable intro points. */
+ if (!hs_client_any_intro_points_usable(&intro_circ->hs_ident->identity_pk,
+ desc)) {
+ goto close;
+ }
+ /* Try to re-extend now. */
+ if (hs_client_reextend_intro_circuit(intro_circ) < 0) {
+ goto close;
+ }
+ /* Success on re-extending. Don't return an error. */
+ ret = 0;
+ goto end;
+
+ close:
+ /* Change the intro circuit purpose before so we don't report an intro point
+ * failure again triggering an extra descriptor fetch. The circuit can
+ * already be closed on failure to re-extend. */
+ if (!TO_CIRCUIT(intro_circ)->marked_for_close) {
+ circuit_change_purpose(TO_CIRCUIT(intro_circ),
+ CIRCUIT_PURPOSE_C_INTRODUCE_ACKED);
+ circuit_mark_for_close(TO_CIRCUIT(intro_circ), END_CIRC_REASON_FINISHED);
+ }
+ /* Close the related rendezvous circuit. */
+ rend_circ = hs_circuitmap_get_rend_circ_client_side(
+ intro_circ->hs_ident->rendezvous_cookie);
+ /* The rendezvous circuit might have collapsed while the INTRODUCE_ACK was
+ * inflight so we can't expect one every time. */
+ if (rend_circ) {
+ circuit_mark_for_close(TO_CIRCUIT(rend_circ), END_CIRC_REASON_FINISHED);
+ }
+
+ end:
+ return ret;
+}
+
+/* Called when we get an INTRODUCE_ACK success status code. Do the appropriate
+ * actions for the rendezvous point and finally close intro_circ. */
+static void
+handle_introduce_ack_success(origin_circuit_t *intro_circ)
+{
+ origin_circuit_t *rend_circ = NULL;
+
+ tor_assert(intro_circ);
+
+ log_info(LD_REND, "Received INTRODUCE_ACK ack! Informing rendezvous");
+
+ /* Get the rendezvous circuit for this rendezvous cookie. */
+ uint8_t *rendezvous_cookie = intro_circ->hs_ident->rendezvous_cookie;
+ rend_circ = hs_circuitmap_get_rend_circ_client_side(rendezvous_cookie);
+ if (rend_circ == NULL) {
+ log_warn(LD_REND, "Can't find any rendezvous circuit. Stopping");
+ goto end;
+ }
+
+ assert_circ_anonymity_ok(rend_circ, get_options());
+ circuit_change_purpose(TO_CIRCUIT(rend_circ),
+ CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED);
+ /* Set timestamp_dirty, because circuit_expire_building expects it to
+ * specify when a circuit entered the
+ * CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED state. */
+ TO_CIRCUIT(rend_circ)->timestamp_dirty = time(NULL);
+
+ end:
+ /* We don't need the intro circuit anymore. It did what it had to do! */
+ circuit_change_purpose(TO_CIRCUIT(intro_circ),
+ CIRCUIT_PURPOSE_C_INTRODUCE_ACKED);
+ circuit_mark_for_close(TO_CIRCUIT(intro_circ), END_CIRC_REASON_FINISHED);
+
+ /* XXX: Close pending intro circuits we might have in parallel. */
+ return;
+}
+
+/* Called when we get an INTRODUCE_ACK failure status code. Depending on our
+ * failure cache status, either close the circuit or re-extend to a new
+ * introduction point. */
+static void
+handle_introduce_ack_bad(origin_circuit_t *circ, int status)
+{
+ tor_assert(circ);
+
+ log_info(LD_REND, "Received INTRODUCE_ACK nack by %s. Reason: %u",
+ safe_str_client(extend_info_describe(circ->build_state->chosen_exit)),
+ status);
+
+ /* It's a NAK. The introduction point didn't relay our request. */
+ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_INTRODUCING);
+
+ /* Note down this failure in the intro point failure cache. Depending on how
+ * many times we've tried this intro point, close it or reextend. */
+ hs_cache_client_intro_state_note(&circ->hs_ident->identity_pk,
+ &circ->hs_ident->intro_auth_pk,
+ INTRO_POINT_FAILURE_GENERIC);
+}
+
+/* Called when we get an INTRODUCE_ACK on the intro circuit circ. The encoded
+ * cell is in payload of length payload_len. Return 0 on success else a
+ * negative value. The circuit is either close or reuse to re-extend to a new
+ * introduction point. */
+static int
+handle_introduce_ack(origin_circuit_t *circ, const uint8_t *payload,
+ size_t payload_len)
+{
+ int status, ret = -1;
+
+ tor_assert(circ);
+ tor_assert(circ->build_state);
+ tor_assert(circ->build_state->chosen_exit);
+ assert_circ_anonymity_ok(circ, get_options());
+ tor_assert(payload);
+
+ status = hs_cell_parse_introduce_ack(payload, payload_len);
+ switch (status) {
+ case HS_CELL_INTRO_ACK_SUCCESS:
+ ret = 0;
+ handle_introduce_ack_success(circ);
+ goto end;
+ case HS_CELL_INTRO_ACK_FAILURE:
+ case HS_CELL_INTRO_ACK_BADFMT:
+ case HS_CELL_INTRO_ACK_NORELAY:
+ handle_introduce_ack_bad(circ, status);
+ /* We are going to see if we have to close the circuits (IP and RP) or we
+ * can re-extend to a new intro point. */
+ ret = close_or_reextend_intro_circ(circ);
+ break;
+ default:
+ log_info(LD_PROTOCOL, "Unknown INTRODUCE_ACK status code %u from %s",
+ status,
+ safe_str_client(extend_info_describe(circ->build_state->chosen_exit)));
+ break;
+ }
+
+ end:
+ return ret;
+}
+
+/* Called when we get a RENDEZVOUS2 cell on the rendezvous circuit circ. The
+ * encoded cell is in payload of length payload_len. Return 0 on success or a
+ * negative value on error. On error, the circuit is marked for close. */
+static int
+handle_rendezvous2(origin_circuit_t *circ, const uint8_t *payload,
+ size_t payload_len)
+{
+ int ret = -1;
+ curve25519_public_key_t server_pk;
+ uint8_t auth_mac[DIGEST256_LEN] = {0};
+ uint8_t handshake_info[CURVE25519_PUBKEY_LEN + sizeof(auth_mac)] = {0};
+ hs_ntor_rend_cell_keys_t keys;
+ const hs_ident_circuit_t *ident;
+
+ tor_assert(circ);
+ tor_assert(payload);
+
+ /* Make things easier. */
+ ident = circ->hs_ident;
+ tor_assert(ident);
+
+ if (hs_cell_parse_rendezvous2(payload, payload_len, handshake_info,
+ sizeof(handshake_info)) < 0) {
+ goto err;
+ }
+ /* Get from the handshake info the SERVER_PK and AUTH_MAC. */
+ memcpy(&server_pk, handshake_info, CURVE25519_PUBKEY_LEN);
+ memcpy(auth_mac, handshake_info + CURVE25519_PUBKEY_LEN, sizeof(auth_mac));
+
+ /* Generate the handshake info. */
+ if (hs_ntor_client_get_rendezvous1_keys(&ident->intro_auth_pk,
+ &ident->rendezvous_client_kp,
+ &ident->intro_enc_pk, &server_pk,
+ &keys) < 0) {
+ log_info(LD_REND, "Unable to compute the rendezvous keys.");
+ goto err;
+ }
+
+ /* Critical check, make sure that the MAC matches what we got with what we
+ * computed just above. */
+ if (!hs_ntor_client_rendezvous2_mac_is_good(&keys, auth_mac)) {
+ log_info(LD_REND, "Invalid MAC in RENDEZVOUS2. Rejecting cell.");
+ goto err;
+ }
+
+ /* Setup the e2e encryption on the circuit and finalize its state. */
+ if (hs_circuit_setup_e2e_rend_circ(circ, keys.ntor_key_seed,
+ sizeof(keys.ntor_key_seed), 0) < 0) {
+ log_info(LD_REND, "Unable to setup the e2e encryption.");
+ goto err;
+ }
+ /* Success. Hidden service connection finalized! */
+ ret = 0;
+ goto end;
+
+ err:
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
+ end:
+ memwipe(&keys, 0, sizeof(keys));
+ return ret;
+}
+
+/* ========== */
+/* Public API */
+/* ========== */
+
+/** A circuit just finished connecting to a hidden service that the stream
+ * <b>conn</b> has been waiting for. Let the HS subsystem know about this. */
+void
+hs_client_note_connection_attempt_succeeded(const edge_connection_t *conn)
+{
+ tor_assert(connection_edge_is_rendezvous_stream(conn));
+
+ if (BUG(conn->rend_data && conn->hs_ident)) {
+ log_warn(LD_BUG, "Stream had both rend_data and hs_ident..."
+ "Prioritizing hs_ident");
+ }
+
+ if (conn->hs_ident) { /* It's v3: pass it to the prop224 handler */
+ note_connection_attempt_succeeded(conn->hs_ident);
+ return;
+ } else if (conn->rend_data) { /* It's v2: pass it to the legacy handler */
+ rend_client_note_connection_attempt_ended(conn->rend_data);
+ return;
+ }
+}
+
+/* With the given encoded descriptor in desc_str and the service key in
+ * service_identity_pk, decode the descriptor and set the desc pointer with a
+ * newly allocated descriptor object.
+ *
+ * Return 0 on success else a negative value and desc is set to NULL. */
+int
+hs_client_decode_descriptor(const char *desc_str,
+ const ed25519_public_key_t *service_identity_pk,
+ hs_descriptor_t **desc)
+{
+ int ret;
+ uint8_t subcredential[DIGEST256_LEN];
+ ed25519_public_key_t blinded_pubkey;
+
+ tor_assert(desc_str);
+ tor_assert(service_identity_pk);
+ tor_assert(desc);
+
+ /* Create subcredential for this HS so that we can decrypt */
+ {
+ uint64_t current_time_period = hs_get_time_period_num(approx_time());
+ hs_build_blinded_pubkey(service_identity_pk, NULL, 0, current_time_period,
+ &blinded_pubkey);
+ hs_get_subcredential(service_identity_pk, &blinded_pubkey, subcredential);
+ }
+
+ /* Parse descriptor */
+ ret = hs_desc_decode_descriptor(desc_str, subcredential, desc);
+ memwipe(subcredential, 0, sizeof(subcredential));
+ if (ret < 0) {
+ log_warn(LD_GENERAL, "Could not parse received descriptor as client");
+ goto err;
+ }
+
+ /* Make sure the descriptor signing key cross certifies with the computed
+ * blinded key. Without this validation, anyone knowing the subcredential
+ * and onion address can forge a descriptor. */
+ if (tor_cert_checksig((*desc)->plaintext_data.signing_key_cert,
+ &blinded_pubkey, approx_time()) < 0) {
+ log_warn(LD_GENERAL, "Descriptor signing key certificate signature "
+ "doesn't validate with computed blinded key.");
+ goto err;
+ }
+
+ return 0;
+ err:
+ return -1;
+}
+
+/* Return true iff there are at least one usable intro point in the service
+ * descriptor desc. */
+int
+hs_client_any_intro_points_usable(const ed25519_public_key_t *service_pk,
+ const hs_descriptor_t *desc)
+{
+ tor_assert(service_pk);
+ tor_assert(desc);
+
+ SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points,
+ const hs_desc_intro_point_t *, ip) {
+ if (intro_point_is_usable(service_pk, ip)) {
+ goto usable;
+ }
+ } SMARTLIST_FOREACH_END(ip);
+
+ return 0;
+ usable:
+ return 1;
+}
+
+/** Launch a connection to a hidden service directory to fetch a hidden
+ * service descriptor using <b>identity_pk</b> to get the necessary keys.
+ *
+ * On success, 1 is returned. If no hidden service is left to ask, return 0.
+ * On error, -1 is returned. (retval is only used by unittests right now) */
+int
+hs_client_refetch_hsdesc(const ed25519_public_key_t *identity_pk)
+{
+ tor_assert(identity_pk);
+
+ /* Are we configured to fetch descriptors? */
+ if (!get_options()->FetchHidServDescriptors) {
+ log_warn(LD_REND, "We received an onion address for a hidden service "
+ "descriptor but we are configured to not fetch.");
+ return 0;
+ }
+
+ /* Check if fetching a desc for this HS is useful to us right now */
+ {
+ const hs_descriptor_t *cached_desc = NULL;
+ cached_desc = hs_cache_lookup_as_client(identity_pk);
+ if (cached_desc && hs_client_any_intro_points_usable(identity_pk,
+ cached_desc)) {
+ log_warn(LD_GENERAL, "We would fetch a v3 hidden service descriptor "
+ "but we already have a useable descriprot.");
+ return 0;
+ }
+ }
+
+ return fetch_v3_desc(identity_pk);
+}
+
+/* This is called when we are trying to attach an AP connection to these
+ * hidden service circuits from connection_ap_handshake_attach_circuit().
+ * Return 0 on success, -1 for a transient error that is actions were
+ * triggered to recover or -2 for a permenent error where both circuits will
+ * marked for close.
+ *
+ * The following supports every hidden service version. */
+int
+hs_client_send_introduce1(origin_circuit_t *intro_circ,
+ origin_circuit_t *rend_circ)
+{
+ return (intro_circ->hs_ident) ? send_introduce1(intro_circ, rend_circ) :
+ rend_client_send_introduction(intro_circ,
+ rend_circ);
+}
+
+/* Called when the client circuit circ has been established. It can be either
+ * an introduction or rendezvous circuit. This function handles all hidden
+ * service versions. */
+void
+hs_client_circuit_has_opened(origin_circuit_t *circ)
+{
+ tor_assert(circ);
+
+ /* Handle both version. v2 uses rend_data and v3 uses the hs circuit
+ * identifier hs_ident. Can't be both. */
+ switch (TO_CIRCUIT(circ)->purpose) {
+ case CIRCUIT_PURPOSE_C_INTRODUCING:
+ if (circ->hs_ident) {
+ client_intro_circ_has_opened(circ);
+ } else {
+ rend_client_introcirc_has_opened(circ);
+ }
+ break;
+ case CIRCUIT_PURPOSE_C_ESTABLISH_REND:
+ if (circ->hs_ident) {
+ client_rendezvous_circ_has_opened(circ);
+ } else {
+ rend_client_rendcirc_has_opened(circ);
+ }
+ break;
+ default:
+ tor_assert_nonfatal_unreached();
+ }
+}
+
+/* Called when we receive a RENDEZVOUS_ESTABLISHED cell. Change the state of
+ * the circuit to CIRCUIT_PURPOSE_C_REND_READY. Return 0 on success else a
+ * negative value and the circuit marked for close. */
+int
+hs_client_receive_rendezvous_acked(origin_circuit_t *circ,
+ const uint8_t *payload, size_t payload_len)
+{
+ tor_assert(circ);
+ tor_assert(payload);
+
+ (void) payload_len;
+
+ if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_ESTABLISH_REND) {
+ log_warn(LD_PROTOCOL, "Got a RENDEZVOUS_ESTABLISHED but we were not "
+ "expecting one. Closing circuit.");
+ goto err;
+ }
+
+ log_info(LD_REND, "Received an RENDEZVOUS_ESTABLISHED. This circuit is "
+ "now ready for rendezvous.");
+ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_REND_READY);
+
+ /* Set timestamp_dirty, because circuit_expire_building expects it to
+ * specify when a circuit entered the _C_REND_READY state. */
+ TO_CIRCUIT(circ)->timestamp_dirty = time(NULL);
+
+ /* From a path bias point of view, this circuit is now successfully used.
+ * Waiting any longer opens us up to attacks from malicious hidden services.
+ * They could induce the client to attempt to connect to their hidden
+ * service and never reply to the client's rend requests */
+ pathbias_mark_use_success(circ);
+
+ /* If we already have the introduction circuit built, make sure we send
+ * the INTRODUCE cell _now_ */
+ connection_ap_attach_pending(1);
+
+ return 0;
+ err:
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
+ return -1;
+}
+
+/* This is called when a descriptor has arrived following a fetch request and
+ * has been stored in the client cache. Every entry connection that matches
+ * the service identity key in the ident will get attached to the hidden
+ * service circuit. */
+void
+hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident)
+{
+ time_t now = time(NULL);
+ smartlist_t *conns = NULL;
+
+ tor_assert(ident);
+
+ conns = connection_list_by_type_state(CONN_TYPE_AP,
+ AP_CONN_STATE_RENDDESC_WAIT);
+ SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) {
+ const hs_descriptor_t *desc;
+ entry_connection_t *entry_conn = TO_ENTRY_CONN(base_conn);
+ const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(entry_conn);
+
+ /* Only consider the entry connections that matches the service for which
+ * we just fetched its descriptor. */
+ if (!edge_conn->hs_ident ||
+ !ed25519_pubkey_eq(&ident->identity_pk,
+ &edge_conn->hs_ident->identity_pk)) {
+ continue;
+ }
+ assert_connection_ok(base_conn, now);
+
+ /* We were just called because we stored the descriptor for this service
+ * so not finding a descriptor means we have a bigger problem. */
+ desc = hs_cache_lookup_as_client(&ident->identity_pk);
+ if (BUG(desc == NULL)) {
+ goto end;
+ }
+
+ if (!hs_client_any_intro_points_usable(&ident->identity_pk, desc)) {
+ log_info(LD_REND, "Hidden service descriptor is unusable. "
+ "Closing streams.");
+ connection_mark_unattached_ap(entry_conn,
+ END_STREAM_REASON_RESOLVEFAILED);
+ /* We are unable to use the descriptor so remove the directory request
+ * from the cache so the next connection can try again. */
+ note_connection_attempt_succeeded(edge_conn->hs_ident);
+ goto end;
+ }
+
+ log_info(LD_REND, "Descriptor has arrived. Launching circuits.");
+
+ /* Restart their timeout values, so they get a fair shake at connecting to
+ * the hidden service. XXX: Improve comment on why this is needed. */
+ base_conn->timestamp_created = now;
+ base_conn->timestamp_lastread = now;
+ base_conn->timestamp_lastwritten = now;
+ /* Change connection's state into waiting for a circuit. */
+ base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT;
+
+ connection_ap_mark_as_pending_circuit(entry_conn);
+ } SMARTLIST_FOREACH_END(base_conn);
+
+ end:
+ /* We don't have ownership of the objects in this list. */
+ smartlist_free(conns);
+}
+
+/* Return a newly allocated extend_info_t for a randomly chosen introduction
+ * point for the given edge connection identifier ident. Return NULL if we
+ * can't pick any usable introduction points. */
+extend_info_t *
+hs_client_get_random_intro_from_edge(const edge_connection_t *edge_conn)
+{
+ tor_assert(edge_conn);
+
+ return (edge_conn->hs_ident) ?
+ client_get_random_intro(&edge_conn->hs_ident->identity_pk) :
+ rend_client_get_random_intro(edge_conn->rend_data);
+}
+/* Called when get an INTRODUCE_ACK cell on the introduction circuit circ.
+ * Return 0 on success else a negative value is returned. The circuit will be
+ * closed or reuse to extend again to another intro point. */
+int
+hs_client_receive_introduce_ack(origin_circuit_t *circ,
+ const uint8_t *payload, size_t payload_len)
+{
+ int ret = -1;
+
+ tor_assert(circ);
+ tor_assert(payload);
+
+ if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) {
+ log_warn(LD_PROTOCOL, "Unexpected INTRODUCE_ACK on circuit %u.",
+ (unsigned int) TO_CIRCUIT(circ)->n_circ_id);
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
+ goto end;
+ }
+
+ ret = (circ->hs_ident) ? handle_introduce_ack(circ, payload, payload_len) :
+ rend_client_introduction_acked(circ, payload,
+ payload_len);
+ /* For path bias: This circuit was used successfully. NACK or ACK counts. */
+ pathbias_mark_use_success(circ);
+
+ end:
+ return ret;
+}
+
+/* Called when get a RENDEZVOUS2 cell on the rendezvous circuit circ. Return
+ * 0 on success else a negative value is returned. The circuit will be closed
+ * on error. */
+int
+hs_client_receive_rendezvous2(origin_circuit_t *circ,
+ const uint8_t *payload, size_t payload_len)
+{
+ int ret = -1;
+
+ tor_assert(circ);
+ tor_assert(payload);
+
+ /* Circuit can possibly be in both state because we could receive a
+ * RENDEZVOUS2 cell before the INTRODUCE_ACK has been received. */
+ if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_REND_READY &&
+ TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) {
+ log_warn(LD_PROTOCOL, "Unexpected RENDEZVOUS2 cell on circuit %u. "
+ "Closing circuit.",
+ (unsigned int) TO_CIRCUIT(circ)->n_circ_id);
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
+ goto end;
+ }
+
+ log_info(LD_REND, "Got RENDEZVOUS2 cell from hidden service on circuit %u.",
+ TO_CIRCUIT(circ)->n_circ_id);
+
+ ret = (circ->hs_ident) ? handle_rendezvous2(circ, payload, payload_len) :
+ rend_client_receive_rendezvous(circ, payload,
+ payload_len);
+ end:
+ return ret;
+}
+
+/* Extend the introduction circuit circ to another valid introduction point
+ * for the hidden service it is trying to connect to, or mark it and launch a
+ * new circuit if we can't extend it. Return 0 on success or possible
+ * success. Return -1 and mark the introduction circuit for close on permanent
+ * failure.
+ *
+ * On failure, the caller is responsible for marking the associated rendezvous
+ * circuit for close. */
+int
+hs_client_reextend_intro_circuit(origin_circuit_t *circ)
+{
+ int ret = -1;
+ extend_info_t *ei;
+
+ tor_assert(circ);
+
+ ei = (circ->hs_ident) ?
+ client_get_random_intro(&circ->hs_ident->identity_pk) :
+ rend_client_get_random_intro(circ->rend_data);
+ if (ei == NULL) {
+ log_warn(LD_REND, "No usable introduction points left. Closing.");
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
+ goto end;
+ }
+
+ if (circ->remaining_relay_early_cells) {
+ log_info(LD_REND, "Re-extending circ %u, this time to %s.",
+ (unsigned int) TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(extend_info_describe(ei)));
+ ret = circuit_extend_to_new_exit(circ, ei);
+ if (ret == 0) {
+ /* We were able to extend so update the timestamp so we avoid expiring
+ * this circuit too early. The intro circuit is short live so the
+ * linkability issue is minimized, we just need the circuit to hold a
+ * bit longer so we can introduce. */
+ TO_CIRCUIT(circ)->timestamp_dirty = time(NULL);
+ }
+ } else {
+ log_info(LD_REND, "Closing intro circ %u (out of RELAY_EARLY cells).",
+ (unsigned int) TO_CIRCUIT(circ)->n_circ_id);
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED);
+ /* connection_ap_handshake_attach_circuit will launch a new intro circ. */
+ ret = 0;
+ }
+
+ end:
+ extend_info_free(ei);
+ return ret;
+}
+
diff --git a/src/or/hs_client.h b/src/or/hs_client.h
new file mode 100644
index 0000000000..8ed0501c91
--- /dev/null
+++ b/src/or/hs_client.h
@@ -0,0 +1,50 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_client.h
+ * \brief Header file containing client data for the HS subsytem.
+ **/
+
+#ifndef TOR_HS_CLIENT_H
+#define TOR_HS_CLIENT_H
+
+#include "crypto_ed25519.h"
+#include "hs_descriptor.h"
+#include "hs_ident.h"
+
+void hs_client_note_connection_attempt_succeeded(
+ const edge_connection_t *conn);
+
+int hs_client_decode_descriptor(
+ const char *desc_str,
+ const ed25519_public_key_t *service_identity_pk,
+ hs_descriptor_t **desc);
+int hs_client_any_intro_points_usable(const ed25519_public_key_t *service_pk,
+ const hs_descriptor_t *desc);
+int hs_client_refetch_hsdesc(const ed25519_public_key_t *identity_pk);
+
+int hs_client_send_introduce1(origin_circuit_t *intro_circ,
+ origin_circuit_t *rend_circ);
+
+void hs_client_circuit_has_opened(origin_circuit_t *circ);
+
+int hs_client_receive_rendezvous_acked(origin_circuit_t *circ,
+ const uint8_t *payload,
+ size_t payload_len);
+int hs_client_receive_introduce_ack(origin_circuit_t *circ,
+ const uint8_t *payload,
+ size_t payload_len);
+int hs_client_receive_rendezvous2(origin_circuit_t *circ,
+ const uint8_t *payload,
+ size_t payload_len);
+
+void hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident);
+
+extend_info_t *hs_client_get_random_intro_from_edge(
+ const edge_connection_t *edge_conn);
+
+int hs_client_reextend_intro_circuit(origin_circuit_t *circ);
+
+#endif /* TOR_HS_CLIENT_H */
+
diff --git a/src/or/hs_common.c b/src/or/hs_common.c
index 42508126f8..03dd07f6ca 100644
--- a/src/or/hs_common.c
+++ b/src/or/hs_common.c
@@ -14,9 +14,142 @@
#include "or.h"
#include "config.h"
+#include "circuitbuild.h"
#include "networkstatus.h"
+#include "nodelist.h"
+#include "hs_cache.h"
#include "hs_common.h"
+#include "hs_ident.h"
+#include "hs_service.h"
+#include "policies.h"
#include "rendcommon.h"
+#include "rendservice.h"
+#include "routerset.h"
+#include "router.h"
+#include "routerset.h"
+#include "shared_random.h"
+#include "shared_random_state.h"
+
+/* Trunnel */
+#include "ed25519_cert.h"
+
+/* Ed25519 Basepoint value. Taken from section 5 of
+ * https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-03 */
+static const char *str_ed25519_basepoint =
+ "(15112221349535400772501151409588531511"
+ "454012693041857206046113283949847762202, "
+ "463168356949264781694283940034751631413"
+ "07993866256225615783033603165251855960)";
+
+#ifdef HAVE_SYS_UN_H
+
+/** Given <b>ports</b>, a smarlist containing rend_service_port_config_t,
+ * add the given <b>p</b>, a AF_UNIX port to the list. Return 0 on success
+ * else return -ENOSYS if AF_UNIX is not supported (see function in the
+ * #else statement below). */
+static int
+add_unix_port(smartlist_t *ports, rend_service_port_config_t *p)
+{
+ tor_assert(ports);
+ tor_assert(p);
+ tor_assert(p->is_unix_addr);
+
+ smartlist_add(ports, p);
+ return 0;
+}
+
+/** Given <b>conn</b> set it to use the given port <b>p</b> values. Return 0
+ * on success else return -ENOSYS if AF_UNIX is not supported (see function
+ * in the #else statement below). */
+static int
+set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p)
+{
+ tor_assert(conn);
+ tor_assert(p);
+ tor_assert(p->is_unix_addr);
+
+ conn->base_.socket_family = AF_UNIX;
+ tor_addr_make_unspec(&conn->base_.addr);
+ conn->base_.port = 1;
+ conn->base_.address = tor_strdup(p->unix_addr);
+ return 0;
+}
+
+#else /* defined(HAVE_SYS_UN_H) */
+
+static int
+set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p)
+{
+ (void) conn;
+ (void) p;
+ return -ENOSYS;
+}
+
+static int
+add_unix_port(smartlist_t *ports, rend_service_port_config_t *p)
+{
+ (void) ports;
+ (void) p;
+ return -ENOSYS;
+}
+
+#endif /* HAVE_SYS_UN_H */
+
+/* Helper function: The key is a digest that we compare to a node_t object
+ * current hsdir_index. */
+static int
+compare_digest_to_current_hsdir_index(const void *_key, const void **_member)
+{
+ const char *key = _key;
+ const node_t *node = *_member;
+ return tor_memcmp(key, node->hsdir_index->current, DIGEST256_LEN);
+}
+
+/* Helper function: The key is a digest that we compare to a node_t object
+ * next hsdir_index. */
+static int
+compare_digest_to_next_hsdir_index(const void *_key, const void **_member)
+{
+ const char *key = _key;
+ const node_t *node = *_member;
+ return tor_memcmp(key, node->hsdir_index->next, DIGEST256_LEN);
+}
+
+/* Helper function: Compare two node_t objects current hsdir_index. */
+static int
+compare_node_current_hsdir_index(const void **a, const void **b)
+{
+ const node_t *node1= *a;
+ const node_t *node2 = *b;
+ return tor_memcmp(node1->hsdir_index->current,
+ node2->hsdir_index->current,
+ DIGEST256_LEN);
+}
+
+/* Helper function: Compare two node_t objects next hsdir_index. */
+static int
+compare_node_next_hsdir_index(const void **a, const void **b)
+{
+ const node_t *node1= *a;
+ const node_t *node2 = *b;
+ return tor_memcmp(node1->hsdir_index->next,
+ node2->hsdir_index->next,
+ DIGEST256_LEN);
+}
+
+/* Allocate and return a string containing the path to filename in directory.
+ * This function will never return NULL. The caller must free this path. */
+char *
+hs_path_from_filename(const char *directory, const char *filename)
+{
+ char *file_path = NULL;
+
+ tor_assert(directory);
+ tor_assert(filename);
+
+ tor_asprintf(&file_path, "%s%s%s", directory, PATH_SEPARATOR, filename);
+ return file_path;
+}
/* Make sure that the directory for <b>service</b> is private, using the config
* <b>username</b>.
@@ -56,6 +189,17 @@ hs_check_service_private_dir(const char *username, const char *path,
STATIC uint64_t
get_time_period_length(void)
{
+ /* If we are on a test network, make the time period smaller than normal so
+ that we actually see it rotate. Specifically, make it the same length as
+ an SRV protocol run. */
+ if (get_options()->TestingTorNetwork) {
+ unsigned run_duration = sr_state_get_protocol_run_duration();
+ /* An SRV run should take more than a minute (it's 24 rounds) */
+ tor_assert_nonfatal(run_duration > 60);
+ /* Turn it from seconds to minutes before returning: */
+ return sr_state_get_protocol_run_duration() / 60;
+ }
+
int32_t time_period_length = networkstatus_get_param(NULL, "hsdir-interval",
HS_TIME_PERIOD_LENGTH_DEFAULT,
HS_TIME_PERIOD_LENGTH_MIN,
@@ -67,17 +211,22 @@ get_time_period_length(void)
}
/** Get the HS time period number at time <b>now</b> */
-STATIC uint64_t
-get_time_period_num(time_t now)
+uint64_t
+hs_get_time_period_num(time_t now)
{
uint64_t time_period_num;
+
+ /* Start by calculating minutes since the epoch */
uint64_t time_period_length = get_time_period_length();
uint64_t minutes_since_epoch = now / 60;
- /* Now subtract half a day to fit the prop224 time period schedule (see
- * section [TIME-PERIODS]). */
- tor_assert(minutes_since_epoch > HS_TIME_PERIOD_ROTATION_OFFSET);
- minutes_since_epoch -= HS_TIME_PERIOD_ROTATION_OFFSET;
+ /* Apply the rotation offset as specified by prop224 (section
+ * [TIME-PERIODS]), so that new time periods synchronize nicely with SRV
+ * publication */
+ unsigned int time_period_rotation_offset = sr_state_get_phase_duration();
+ time_period_rotation_offset /= 60; /* go from seconds to minutes */
+ tor_assert(minutes_since_epoch > time_period_rotation_offset);
+ minutes_since_epoch -= time_period_rotation_offset;
/* Calculate the time period */
time_period_num = minutes_since_epoch / time_period_length;
@@ -89,7 +238,22 @@ get_time_period_num(time_t now)
uint64_t
hs_get_next_time_period_num(time_t now)
{
- return get_time_period_num(now) + 1;
+ return hs_get_time_period_num(now) + 1;
+}
+
+/* Return the start time of the upcoming time period based on <b>now</b>. */
+time_t
+hs_get_start_time_of_next_time_period(time_t now)
+{
+ uint64_t time_period_length = get_time_period_length();
+
+ /* Get start time of next time period */
+ uint64_t next_time_period_num = hs_get_next_time_period_num(now);
+ uint64_t start_of_next_tp_in_mins = next_time_period_num *time_period_length;
+
+ /* Apply rotation offset as specified by prop224 section [TIME-PERIODS] */
+ unsigned int time_period_rotation_offset = sr_state_get_phase_duration();
+ return (time_t)(start_of_next_tp_in_mins * 60 + time_period_rotation_offset);
}
/* Create a new rend_data_t for a specific given <b>version</b>.
@@ -344,3 +508,1232 @@ rend_data_get_pk_digest(const rend_data_t *rend_data, size_t *len_out)
}
}
+/* Using the given time period number, compute the disaster shared random
+ * value and put it in srv_out. It MUST be at least DIGEST256_LEN bytes. */
+static void
+compute_disaster_srv(uint64_t time_period_num, uint8_t *srv_out)
+{
+ crypto_digest_t *digest;
+
+ tor_assert(srv_out);
+
+ digest = crypto_digest256_new(DIGEST_SHA3_256);
+
+ /* Start setting up payload:
+ * H("shared-random-disaster" | INT_8(period_length) | INT_8(period_num)) */
+ crypto_digest_add_bytes(digest, HS_SRV_DISASTER_PREFIX,
+ HS_SRV_DISASTER_PREFIX_LEN);
+
+ /* Setup INT_8(period_length) | INT_8(period_num) */
+ {
+ uint64_t time_period_length = get_time_period_length();
+ char period_stuff[sizeof(uint64_t)*2];
+ size_t offset = 0;
+ set_uint64(period_stuff, tor_htonll(time_period_length));
+ offset += sizeof(uint64_t);
+ set_uint64(period_stuff+offset, tor_htonll(time_period_num));
+ offset += sizeof(uint64_t);
+ tor_assert(offset == sizeof(period_stuff));
+
+ crypto_digest_add_bytes(digest, period_stuff, sizeof(period_stuff));
+ }
+
+ crypto_digest_get_digest(digest, (char *) srv_out, DIGEST256_LEN);
+ crypto_digest_free(digest);
+}
+
+/** Due to the high cost of computing the disaster SRV and that potentially we
+ * would have to do it thousands of times in a row, we always cache the
+ * computer disaster SRV (and its corresponding time period num) in case we
+ * want to reuse it soon after. We need to cache two SRVs, one for each active
+ * time period (in case of overlap mode).
+ */
+static uint8_t cached_disaster_srv[2][DIGEST256_LEN];
+static uint64_t cached_time_period_nums[2] = {0};
+
+/** Compute the disaster SRV value for this <b>time_period_num</b> and put it
+ * in <b>srv_out</b> (of size at least DIGEST256_LEN). First check our caches
+ * to see if we have already computed it. */
+STATIC void
+get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out)
+{
+ if (time_period_num == cached_time_period_nums[0]) {
+ memcpy(srv_out, cached_disaster_srv[0], DIGEST256_LEN);
+ return;
+ } else if (time_period_num == cached_time_period_nums[1]) {
+ memcpy(srv_out, cached_disaster_srv[1], DIGEST256_LEN);
+ return;
+ } else {
+ int replace_idx;
+ // Replace the lower period number.
+ if (cached_time_period_nums[0] <= cached_time_period_nums[1]) {
+ replace_idx = 0;
+ } else {
+ replace_idx = 1;
+ }
+ cached_time_period_nums[replace_idx] = time_period_num;
+ compute_disaster_srv(time_period_num, cached_disaster_srv[replace_idx]);
+ memcpy(srv_out, cached_disaster_srv[replace_idx], DIGEST256_LEN);
+ return;
+ }
+}
+
+#ifdef TOR_UNIT_TESTS
+
+/** Get the first cached disaster SRV. Only used by unittests. */
+STATIC uint8_t *
+get_first_cached_disaster_srv(void)
+{
+ return cached_disaster_srv[0];
+}
+
+/** Get the second cached disaster SRV. Only used by unittests. */
+STATIC uint8_t *
+get_second_cached_disaster_srv(void)
+{
+ return cached_disaster_srv[1];
+}
+
+#endif
+
+/* When creating a blinded key, we need a parameter which construction is as
+ * follow: H(pubkey | [secret] | ed25519-basepoint | nonce).
+ *
+ * The nonce has a pre-defined format which uses the time period number
+ * period_num and the start of the period in second start_time_period.
+ *
+ * The secret of size secret_len is optional meaning that it can be NULL and
+ * thus will be ignored for the param construction.
+ *
+ * The result is put in param_out. */
+static void
+build_blinded_key_param(const ed25519_public_key_t *pubkey,
+ const uint8_t *secret, size_t secret_len,
+ uint64_t period_num, uint64_t period_length,
+ uint8_t *param_out)
+{
+ size_t offset = 0;
+ const char blind_str[] = "Derive temporary signing key";
+ uint8_t nonce[HS_KEYBLIND_NONCE_LEN];
+ crypto_digest_t *digest;
+
+ tor_assert(pubkey);
+ tor_assert(param_out);
+
+ /* Create the nonce N. The construction is as follow:
+ * N = "key-blind" || INT_8(period_num) || INT_8(period_length) */
+ memcpy(nonce, HS_KEYBLIND_NONCE_PREFIX, HS_KEYBLIND_NONCE_PREFIX_LEN);
+ offset += HS_KEYBLIND_NONCE_PREFIX_LEN;
+ set_uint64(nonce + offset, tor_htonll(period_num));
+ offset += sizeof(uint64_t);
+ set_uint64(nonce + offset, tor_htonll(period_length));
+ offset += sizeof(uint64_t);
+ tor_assert(offset == HS_KEYBLIND_NONCE_LEN);
+
+ /* Generate the parameter h and the construction is as follow:
+ * h = H(BLIND_STRING | pubkey | [secret] | ed25519-basepoint | N) */
+ digest = crypto_digest256_new(DIGEST_SHA3_256);
+ crypto_digest_add_bytes(digest, blind_str, sizeof(blind_str));
+ crypto_digest_add_bytes(digest, (char *) pubkey, ED25519_PUBKEY_LEN);
+ /* Optional secret. */
+ if (secret) {
+ crypto_digest_add_bytes(digest, (char *) secret, secret_len);
+ }
+ crypto_digest_add_bytes(digest, str_ed25519_basepoint,
+ strlen(str_ed25519_basepoint));
+ crypto_digest_add_bytes(digest, (char *) nonce, sizeof(nonce));
+
+ /* Extract digest and put it in the param. */
+ crypto_digest_get_digest(digest, (char *) param_out, DIGEST256_LEN);
+ crypto_digest_free(digest);
+
+ memwipe(nonce, 0, sizeof(nonce));
+}
+
+/* Using an ed25519 public key and version to build the checksum of an
+ * address. Put in checksum_out. Format is:
+ * SHA3-256(".onion checksum" || PUBKEY || VERSION)
+ *
+ * checksum_out must be large enough to receive 32 bytes (DIGEST256_LEN). */
+static void
+build_hs_checksum(const ed25519_public_key_t *key, uint8_t version,
+ uint8_t *checksum_out)
+{
+ size_t offset = 0;
+ char data[HS_SERVICE_ADDR_CHECKSUM_INPUT_LEN];
+
+ /* Build checksum data. */
+ memcpy(data, HS_SERVICE_ADDR_CHECKSUM_PREFIX,
+ HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN);
+ offset += HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN;
+ memcpy(data + offset, key->pubkey, ED25519_PUBKEY_LEN);
+ offset += ED25519_PUBKEY_LEN;
+ set_uint8(data + offset, version);
+ offset += sizeof(version);
+ tor_assert(offset == HS_SERVICE_ADDR_CHECKSUM_INPUT_LEN);
+
+ /* Hash the data payload to create the checksum. */
+ crypto_digest256((char *) checksum_out, data, sizeof(data),
+ DIGEST_SHA3_256);
+}
+
+/* Using an ed25519 public key, checksum and version to build the binary
+ * representation of a service address. Put in addr_out. Format is:
+ * addr_out = PUBKEY || CHECKSUM || VERSION
+ *
+ * addr_out must be large enough to receive HS_SERVICE_ADDR_LEN bytes. */
+static void
+build_hs_address(const ed25519_public_key_t *key, const uint8_t *checksum,
+ uint8_t version, char *addr_out)
+{
+ size_t offset = 0;
+
+ tor_assert(key);
+ tor_assert(checksum);
+
+ memcpy(addr_out, key->pubkey, ED25519_PUBKEY_LEN);
+ offset += ED25519_PUBKEY_LEN;
+ memcpy(addr_out + offset, checksum, HS_SERVICE_ADDR_CHECKSUM_LEN_USED);
+ offset += HS_SERVICE_ADDR_CHECKSUM_LEN_USED;
+ set_uint8(addr_out + offset, version);
+ offset += sizeof(uint8_t);
+ tor_assert(offset == HS_SERVICE_ADDR_LEN);
+}
+
+/* Helper for hs_parse_address(): Using a binary representation of a service
+ * address, parse its content into the key_out, checksum_out and version_out.
+ * Any out variable can be NULL in case the caller would want only one field.
+ * checksum_out MUST at least be 2 bytes long. address must be at least
+ * HS_SERVICE_ADDR_LEN bytes but doesn't need to be NUL terminated. */
+static void
+hs_parse_address_impl(const char *address, ed25519_public_key_t *key_out,
+ uint8_t *checksum_out, uint8_t *version_out)
+{
+ size_t offset = 0;
+
+ tor_assert(address);
+
+ if (key_out) {
+ /* First is the key. */
+ memcpy(key_out->pubkey, address, ED25519_PUBKEY_LEN);
+ }
+ offset += ED25519_PUBKEY_LEN;
+ if (checksum_out) {
+ /* Followed by a 2 bytes checksum. */
+ memcpy(checksum_out, address + offset, HS_SERVICE_ADDR_CHECKSUM_LEN_USED);
+ }
+ offset += HS_SERVICE_ADDR_CHECKSUM_LEN_USED;
+ if (version_out) {
+ /* Finally, version value is 1 byte. */
+ *version_out = get_uint8(address + offset);
+ }
+ offset += sizeof(uint8_t);
+ /* Extra safety. */
+ tor_assert(offset == HS_SERVICE_ADDR_LEN);
+}
+
+/* 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).
+ * 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)
+{
+ uint8_t credential[DIGEST256_LEN];
+ crypto_digest_t *digest;
+
+ tor_assert(identity_pk);
+ tor_assert(blinded_pk);
+ tor_assert(subcred_out);
+
+ /* First, build the credential. Construction is as follow:
+ * credential = H("credential" | public-identity-key) */
+ digest = crypto_digest256_new(DIGEST_SHA3_256);
+ crypto_digest_add_bytes(digest, HS_CREDENTIAL_PREFIX,
+ HS_CREDENTIAL_PREFIX_LEN);
+ crypto_digest_add_bytes(digest, (const char *) identity_pk->pubkey,
+ ED25519_PUBKEY_LEN);
+ crypto_digest_get_digest(digest, (char *) credential, DIGEST256_LEN);
+ crypto_digest_free(digest);
+
+ /* Now, compute the subcredential. Construction is as follow:
+ * subcredential = H("subcredential" | credential | blinded-public-key). */
+ digest = crypto_digest256_new(DIGEST_SHA3_256);
+ crypto_digest_add_bytes(digest, HS_SUBCREDENTIAL_PREFIX,
+ HS_SUBCREDENTIAL_PREFIX_LEN);
+ crypto_digest_add_bytes(digest, (const char *) credential,
+ 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_free(digest);
+
+ memwipe(credential, 0, sizeof(credential));
+}
+
+/* From the given list of hidden service ports, find the ones that much the
+ * given edge connection conn, pick one at random and use it to set the
+ * connection address. Return 0 on success or -1 if none. */
+int
+hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn)
+{
+ rend_service_port_config_t *chosen_port;
+ unsigned int warn_once = 0;
+ smartlist_t *matching_ports;
+
+ tor_assert(ports);
+ tor_assert(conn);
+
+ matching_ports = smartlist_new();
+ SMARTLIST_FOREACH_BEGIN(ports, rend_service_port_config_t *, p) {
+ if (TO_CONN(conn)->port != p->virtual_port) {
+ continue;
+ }
+ if (!(p->is_unix_addr)) {
+ smartlist_add(matching_ports, p);
+ } else {
+ if (add_unix_port(matching_ports, p)) {
+ if (!warn_once) {
+ /* Unix port not supported so warn only once. */
+ log_warn(LD_REND, "Saw AF_UNIX virtual port mapping for port %d "
+ "which is unsupported on this platform. "
+ "Ignoring it.",
+ TO_CONN(conn)->port);
+ }
+ warn_once++;
+ }
+ }
+ } SMARTLIST_FOREACH_END(p);
+
+ chosen_port = smartlist_choose(matching_ports);
+ smartlist_free(matching_ports);
+ if (chosen_port) {
+ if (!(chosen_port->is_unix_addr)) {
+ /* Get a non-AF_UNIX connection ready for connection_exit_connect() */
+ tor_addr_copy(&TO_CONN(conn)->addr, &chosen_port->real_addr);
+ TO_CONN(conn)->port = chosen_port->real_port;
+ } else {
+ if (set_unix_port(conn, chosen_port)) {
+ /* Simply impossible to end up here else we were able to add a Unix
+ * port without AF_UNIX support... ? */
+ tor_assert(0);
+ }
+ }
+ }
+ return (chosen_port) ? 0 : -1;
+}
+
+/* Using a base32 representation of a service address, parse its content into
+ * the key_out, checksum_out and version_out. Any out variable can be NULL in
+ * 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. */
+int
+hs_parse_address(const char *address, ed25519_public_key_t *key_out,
+ uint8_t *checksum_out, uint8_t *version_out)
+{
+ char decoded[HS_SERVICE_ADDR_LEN];
+
+ tor_assert(address);
+
+ /* 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));
+ goto invalid;
+ }
+
+ /* Decode address so we can extract needed fields. */
+ if (base32_decode(decoded, sizeof(decoded), address, strlen(address)) < 0) {
+ log_warn(LD_REND, "Service address %s can't be decoded.",
+ escaped_safe_str(address));
+ goto invalid;
+ }
+
+ /* Parse the decoded address into the fields we need. */
+ hs_parse_address_impl(decoded, key_out, checksum_out, version_out);
+
+ return 0;
+ invalid:
+ return -1;
+}
+
+/* Validate a given onion address. The length, the base32 decoding and
+ * checksum are validated. Return 1 if valid else 0. */
+int
+hs_address_is_valid(const char *address)
+{
+ uint8_t version;
+ uint8_t checksum[HS_SERVICE_ADDR_CHECKSUM_LEN_USED];
+ uint8_t target_checksum[DIGEST256_LEN];
+ ed25519_public_key_t key;
+
+ /* Parse the decoded address into the fields we need. */
+ if (hs_parse_address(address, &key, checksum, &version) < 0) {
+ goto invalid;
+ }
+
+ /* Get the checksum it's suppose to be and compare it with what we have
+ * encoded in the address. */
+ build_hs_checksum(&key, version, target_checksum);
+ if (tor_memcmp(checksum, target_checksum, sizeof(checksum))) {
+ log_warn(LD_REND, "Service address %s invalid checksum.",
+ escaped_safe_str(address));
+ goto invalid;
+ }
+
+ /* Valid address. */
+ return 1;
+ invalid:
+ return 0;
+}
+
+/* Build a service address using an ed25519 public key and a given version.
+ * The returned address is base32 encoded and put in addr_out. The caller MUST
+ * make sure the addr_out is at least HS_SERVICE_ADDR_LEN_BASE32 + 1 long.
+ *
+ * Format is as follow:
+ * base32(PUBKEY || CHECKSUM || VERSION)
+ * CHECKSUM = H(".onion checksum" || PUBKEY || VERSION)
+ * */
+void
+hs_build_address(const ed25519_public_key_t *key, uint8_t version,
+ char *addr_out)
+{
+ uint8_t checksum[DIGEST256_LEN];
+ char address[HS_SERVICE_ADDR_LEN];
+
+ tor_assert(key);
+ tor_assert(addr_out);
+
+ /* Get the checksum of the address. */
+ build_hs_checksum(key, version, checksum);
+ /* Get the binary address representation. */
+ build_hs_address(key, checksum, version, address);
+
+ /* Encode the address. addr_out will be NUL terminated after this. */
+ base32_encode(addr_out, HS_SERVICE_ADDR_LEN_BASE32 + 1, address,
+ sizeof(address));
+ /* Validate what we just built. */
+ tor_assert(hs_address_is_valid(addr_out));
+}
+
+/* Return a newly allocated copy of lspec. */
+link_specifier_t *
+hs_link_specifier_dup(const link_specifier_t *lspec)
+{
+ link_specifier_t *dup = link_specifier_new();
+ memcpy(dup, lspec, sizeof(*dup));
+ /* The unrecognized field is a dynamic array so make sure to copy its
+ * content and not the pointer. */
+ link_specifier_setlen_un_unrecognized(
+ dup, link_specifier_getlen_un_unrecognized(lspec));
+ if (link_specifier_getlen_un_unrecognized(dup)) {
+ memcpy(link_specifier_getarray_un_unrecognized(dup),
+ link_specifier_getconstarray_un_unrecognized(lspec),
+ link_specifier_getlen_un_unrecognized(dup));
+ }
+ return dup;
+}
+
+/* From a given ed25519 public key pk and an optional secret, compute a
+ * blinded public key and put it in blinded_pk_out. This is only useful to
+ * the client side because the client only has access to the identity public
+ * key of the service. */
+void
+hs_build_blinded_pubkey(const ed25519_public_key_t *pk,
+ const uint8_t *secret, size_t secret_len,
+ uint64_t time_period_num,
+ ed25519_public_key_t *blinded_pk_out)
+{
+ /* Our blinding key API requires a 32 bytes parameter. */
+ uint8_t param[DIGEST256_LEN];
+
+ tor_assert(pk);
+ tor_assert(blinded_pk_out);
+ tor_assert(!tor_mem_is_zero((char *) pk, ED25519_PUBKEY_LEN));
+
+ build_blinded_key_param(pk, secret, secret_len,
+ time_period_num, get_time_period_length(), param);
+ ed25519_public_blind(blinded_pk_out, pk, param);
+
+ memwipe(param, 0, sizeof(param));
+}
+
+/* From a given ed25519 keypair kp and an optional secret, compute a blinded
+ * keypair for the current time period and put it in blinded_kp_out. This is
+ * only useful by the service side because the client doesn't have access to
+ * the identity secret key. */
+void
+hs_build_blinded_keypair(const ed25519_keypair_t *kp,
+ const uint8_t *secret, size_t secret_len,
+ uint64_t time_period_num,
+ ed25519_keypair_t *blinded_kp_out)
+{
+ /* Our blinding key API requires a 32 bytes parameter. */
+ uint8_t param[DIGEST256_LEN];
+
+ tor_assert(kp);
+ tor_assert(blinded_kp_out);
+ /* Extra safety. A zeroed key is bad. */
+ tor_assert(!tor_mem_is_zero((char *) &kp->pubkey, ED25519_PUBKEY_LEN));
+ tor_assert(!tor_mem_is_zero((char *) &kp->seckey, ED25519_SECKEY_LEN));
+
+ build_blinded_key_param(&kp->pubkey, secret, secret_len,
+ time_period_num, get_time_period_length(), param);
+ ed25519_keypair_blind(blinded_kp_out, kp, param);
+
+ memwipe(param, 0, sizeof(param));
+}
+
+/* Return true if overlap mode is active given the date in consensus. If
+ * consensus is NULL, then we use the latest live consensus we can find. */
+MOCK_IMPL(int,
+hs_overlap_mode_is_active, (const networkstatus_t *consensus, time_t now))
+{
+ time_t valid_after;
+ time_t srv_start_time, tp_start_time;
+
+ if (!consensus) {
+ consensus = networkstatus_get_live_consensus(now);
+ if (!consensus) {
+ return 0;
+ }
+ }
+
+ /* We consider to be in overlap mode when we are in the period of time
+ * between a fresh SRV and the beginning of the new time period (in the
+ * normal network this is between 00:00 (inclusive) and 12:00 UTC
+ * (exclusive)) */
+ valid_after = consensus->valid_after;
+ srv_start_time =sr_state_get_start_time_of_current_protocol_run(valid_after);
+ tp_start_time = hs_get_start_time_of_next_time_period(srv_start_time);
+
+ if (valid_after >= srv_start_time && valid_after < tp_start_time) {
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Return 1 if any virtual port in ports needs a circuit with good uptime.
+ * Else return 0. */
+int
+hs_service_requires_uptime_circ(const smartlist_t *ports)
+{
+ tor_assert(ports);
+
+ SMARTLIST_FOREACH_BEGIN(ports, rend_service_port_config_t *, p) {
+ if (smartlist_contains_int_as_string(get_options()->LongLivedPorts,
+ p->virtual_port)) {
+ return 1;
+ }
+ } SMARTLIST_FOREACH_END(p);
+ return 0;
+}
+
+/* Build hs_index which is used to find the responsible hsdirs. This index
+ * value is used to select the responsible HSDir where their hsdir_index is
+ * closest to this value.
+ * SHA3-256("store-at-idx" | blinded_public_key |
+ * INT_8(replicanum) | INT_8(period_length) | INT_8(period_num) )
+ *
+ * hs_index_out must be large enough to receive DIGEST256_LEN bytes. */
+void
+hs_build_hs_index(uint64_t replica, const ed25519_public_key_t *blinded_pk,
+ uint64_t period_num, uint8_t *hs_index_out)
+{
+ crypto_digest_t *digest;
+
+ tor_assert(blinded_pk);
+ tor_assert(hs_index_out);
+
+ /* Build hs_index. See construction at top of function comment. */
+ digest = crypto_digest256_new(DIGEST_SHA3_256);
+ crypto_digest_add_bytes(digest, HS_INDEX_PREFIX, HS_INDEX_PREFIX_LEN);
+ crypto_digest_add_bytes(digest, (const char *) blinded_pk->pubkey,
+ ED25519_PUBKEY_LEN);
+
+ /* Now setup INT_8(replicanum) | INT_8(period_length) | INT_8(period_num) */
+ {
+ uint64_t period_length = get_time_period_length();
+ char buf[sizeof(uint64_t)*3];
+ size_t offset = 0;
+ set_uint64(buf, tor_htonll(replica));
+ offset += sizeof(uint64_t);
+ set_uint64(buf+offset, tor_htonll(period_length));
+ offset += sizeof(uint64_t);
+ set_uint64(buf+offset, tor_htonll(period_num));
+ offset += sizeof(uint64_t);
+ tor_assert(offset == sizeof(buf));
+
+ crypto_digest_add_bytes(digest, buf, sizeof(buf));
+ }
+
+ crypto_digest_get_digest(digest, (char *) hs_index_out, DIGEST256_LEN);
+ crypto_digest_free(digest);
+}
+
+/* Build hsdir_index which is used to find the responsible hsdirs. This is the
+ * index value that is compare to the hs_index when selecting an HSDir.
+ * SHA3-256("node-idx" | node_identity |
+ * shared_random_value | INT_8(period_length) | INT_8(period_num) )
+ *
+ * hsdir_index_out must be large enough to receive DIGEST256_LEN bytes. */
+void
+hs_build_hsdir_index(const ed25519_public_key_t *identity_pk,
+ const uint8_t *srv_value, uint64_t period_num,
+ uint8_t *hsdir_index_out)
+{
+ crypto_digest_t *digest;
+
+ tor_assert(identity_pk);
+ tor_assert(srv_value);
+ tor_assert(hsdir_index_out);
+
+ /* Build hsdir_index. See construction at top of function comment. */
+ digest = crypto_digest256_new(DIGEST_SHA3_256);
+ crypto_digest_add_bytes(digest, HSDIR_INDEX_PREFIX, HSDIR_INDEX_PREFIX_LEN);
+ crypto_digest_add_bytes(digest, (const char *) identity_pk->pubkey,
+ ED25519_PUBKEY_LEN);
+ crypto_digest_add_bytes(digest, (const char *) srv_value, DIGEST256_LEN);
+
+ {
+ uint64_t time_period_length = get_time_period_length();
+ char period_stuff[sizeof(uint64_t)*2];
+ size_t offset = 0;
+ set_uint64(period_stuff, tor_htonll(period_num));
+ offset += sizeof(uint64_t);
+ set_uint64(period_stuff+offset, tor_htonll(time_period_length));
+ offset += sizeof(uint64_t);
+ tor_assert(offset == sizeof(period_stuff));
+
+ crypto_digest_add_bytes(digest, period_stuff, sizeof(period_stuff));
+ }
+
+ crypto_digest_get_digest(digest, (char *) hsdir_index_out, DIGEST256_LEN);
+ crypto_digest_free(digest);
+}
+
+/* Return a newly allocated buffer containing the current shared random value
+ * or if not present, a disaster value is computed using the given time period
+ * number. If a consensus is provided in <b>ns</b>, use it to get the SRV
+ * value. This function can't fail. */
+uint8_t *
+hs_get_current_srv(uint64_t time_period_num, const networkstatus_t *ns)
+{
+ uint8_t *sr_value = tor_malloc_zero(DIGEST256_LEN);
+ const sr_srv_t *current_srv = sr_get_current(ns);
+
+ if (current_srv) {
+ memcpy(sr_value, current_srv->value, sizeof(current_srv->value));
+ } else {
+ /* Disaster mode. */
+ get_disaster_srv(time_period_num, sr_value);
+ }
+ return sr_value;
+}
+
+/* Return a newly allocated buffer containing the previous shared random
+ * value or if not present, a disaster value is computed using the given time
+ * period number. This function can't fail. */
+uint8_t *
+hs_get_previous_srv(uint64_t time_period_num, const networkstatus_t *ns)
+{
+ uint8_t *sr_value = tor_malloc_zero(DIGEST256_LEN);
+ const sr_srv_t *previous_srv = sr_get_previous(ns);
+
+ if (previous_srv) {
+ memcpy(sr_value, previous_srv->value, sizeof(previous_srv->value));
+ } else {
+ /* Disaster mode. */
+ get_disaster_srv(time_period_num, sr_value);
+ }
+ return sr_value;
+}
+
+/* Return the number of replicas defined by a consensus parameter or the
+ * default value. */
+int32_t
+hs_get_hsdir_n_replicas(void)
+{
+ /* The [1,16] range is a specification requirement. */
+ return networkstatus_get_param(NULL, "hsdir_n_replicas",
+ HS_DEFAULT_HSDIR_N_REPLICAS, 1, 16);
+}
+
+/* Return the spread fetch value defined by a consensus parameter or the
+ * default value. */
+int32_t
+hs_get_hsdir_spread_fetch(void)
+{
+ /* The [1,128] range is a specification requirement. */
+ return networkstatus_get_param(NULL, "hsdir_spread_fetch",
+ HS_DEFAULT_HSDIR_SPREAD_FETCH, 1, 128);
+}
+
+/* Return the spread store value defined by a consensus parameter or the
+ * default value. */
+int32_t
+hs_get_hsdir_spread_store(void)
+{
+ /* The [1,128] range is a specification requirement. */
+ return networkstatus_get_param(NULL, "hsdir_spread_store",
+ HS_DEFAULT_HSDIR_SPREAD_STORE, 1, 128);
+}
+
+/** <b>node</b> is an HSDir so make sure that we have assigned an hsdir index.
+ * If <b>is_for_next_period</b> is set, also check the next HSDir index field.
+ * Return 0 if everything is as expected, else return -1. */
+static int
+node_has_hsdir_index(const node_t *node, int is_for_next_period)
+{
+ tor_assert(node_supports_v3_hsdir(node));
+
+ /* A node can't have an HSDir index without a descriptor since we need desc
+ * to get its ed25519 key */
+ if (!node_has_descriptor(node)) {
+ return 0;
+ }
+
+ /* At this point, since the node has a desc, this node must also have an
+ * hsdir index. If not, something went wrong, so BUG out. */
+ if (BUG(node->hsdir_index == NULL) ||
+ BUG(tor_mem_is_zero((const char*)node->hsdir_index->current,
+ DIGEST256_LEN))) {
+ return 0;
+ }
+
+ if (is_for_next_period &&
+ BUG(tor_mem_is_zero((const char*)node->hsdir_index->next,
+ DIGEST256_LEN))) {
+ return 0;
+ }
+
+ return 1;
+}
+
+/* For a given blinded key and time period number, get the responsible HSDir
+ * and put their routerstatus_t object in the responsible_dirs list. If
+ * is_next_period is true, the next hsdir_index of the node_t is used. If
+ * is_client is true, the spread fetch consensus parameter is used else the
+ * spread store is used which is only for upload. This function can't fail but
+ * it is possible that the responsible_dirs list contains fewer nodes than
+ * expected.
+ *
+ * This function goes over the latest consensus routerstatus list and sorts it
+ * by their node_t hsdir_index then does a binary search to find the closest
+ * node. All of this makes it a bit CPU intensive so use it wisely. */
+void
+hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk,
+ uint64_t time_period_num, int is_next_period,
+ int is_client, smartlist_t *responsible_dirs)
+{
+ smartlist_t *sorted_nodes;
+ /* The compare function used for the smartlist bsearch. We have two
+ * different depending on is_next_period. */
+ int (*cmp_fct)(const void *, const void **);
+
+ tor_assert(blinded_pk);
+ tor_assert(responsible_dirs);
+
+ sorted_nodes = smartlist_new();
+
+ /* Add every node_t that support HSDir v3 for which we do have a valid
+ * hsdir_index already computed for them for this consensus. */
+ {
+ networkstatus_t *c = networkstatus_get_latest_consensus();
+ if (!c || smartlist_len(c->routerstatus_list) == 0) {
+ log_warn(LD_REND, "No valid consensus so we can't get the responsible "
+ "hidden service directories.");
+ goto done;
+ }
+ SMARTLIST_FOREACH_BEGIN(c->routerstatus_list, const routerstatus_t *, rs) {
+ /* Even though this node_t object won't be modified and should be const,
+ * we can't add const object in a smartlist_t. */
+ node_t *n = node_get_mutable_by_id(rs->identity_digest);
+ tor_assert(n);
+ if (node_supports_v3_hsdir(n) && rs->is_hs_dir) {
+ if (!node_has_hsdir_index(n, is_next_period)) {
+ log_info(LD_GENERAL, "Node %s was found without hsdir index.",
+ node_describe(n));
+ continue;
+ }
+ smartlist_add(sorted_nodes, n);
+ }
+ } SMARTLIST_FOREACH_END(rs);
+ }
+ if (smartlist_len(sorted_nodes) == 0) {
+ log_warn(LD_REND, "No nodes found to be HSDir or supporting v3.");
+ goto done;
+ }
+
+ /* First thing we have to do is sort all node_t by hsdir_index. The
+ * is_next_period tells us if we want the current or the next one. Set the
+ * bsearch compare function also while we are at it. */
+ if (is_next_period) {
+ smartlist_sort(sorted_nodes, compare_node_next_hsdir_index);
+ cmp_fct = compare_digest_to_next_hsdir_index;
+ } else {
+ smartlist_sort(sorted_nodes, compare_node_current_hsdir_index);
+ cmp_fct = compare_digest_to_current_hsdir_index;
+ }
+
+ /* For all replicas, we'll select a set of HSDirs using the consensus
+ * parameters and the sorted list. The replica starting at value 1 is
+ * defined by the specification. */
+ for (int replica = 1; replica <= hs_get_hsdir_n_replicas(); replica++) {
+ int idx, start, found, n_added = 0;
+ uint8_t hs_index[DIGEST256_LEN] = {0};
+ /* Number of node to add to the responsible dirs list depends on if we are
+ * trying to fetch or store. A client always fetches. */
+ int n_to_add = (is_client) ? hs_get_hsdir_spread_fetch() :
+ hs_get_hsdir_spread_store();
+
+ /* Get the index that we should use to select the node. */
+ hs_build_hs_index(replica, blinded_pk, time_period_num, hs_index);
+ /* The compare function pointer has been set correctly earlier. */
+ start = idx = smartlist_bsearch_idx(sorted_nodes, hs_index, cmp_fct,
+ &found);
+ /* Getting the length of the list if no member is greater than the key we
+ * are looking for so start at the first element. */
+ if (idx == smartlist_len(sorted_nodes)) {
+ start = idx = 0;
+ }
+ while (n_added < n_to_add) {
+ const node_t *node = smartlist_get(sorted_nodes, idx);
+ /* If the node has already been selected which is possible between
+ * replicas, the specification says to skip over. */
+ if (!smartlist_contains(responsible_dirs, node->rs)) {
+ smartlist_add(responsible_dirs, node->rs);
+ ++n_added;
+ }
+ if (++idx == smartlist_len(sorted_nodes)) {
+ /* Wrap if we've reached the end of the list. */
+ idx = 0;
+ }
+ if (idx == start) {
+ /* We've gone over the whole list, stop and avoid infinite loop. */
+ break;
+ }
+ }
+ }
+
+ done:
+ smartlist_free(sorted_nodes);
+}
+
+/*********************** HSDir request tracking ***************************/
+
+/** Return the period for which a hidden service directory cannot be queried
+ * for the same descriptor ID again, taking TestingTorNetwork into account. */
+time_t
+hs_hsdir_requery_period(const or_options_t *options)
+{
+ tor_assert(options);
+
+ if (options->TestingTorNetwork) {
+ return REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING;
+ } else {
+ return REND_HID_SERV_DIR_REQUERY_PERIOD;
+ }
+}
+
+/** Tracks requests for fetching hidden service descriptors. It's used by
+ * hidden service clients, to avoid querying HSDirs that have already failed
+ * giving back a descriptor. The same data structure is used to track both v2
+ * and v3 HS descriptor requests.
+ *
+ * The string map is a key/value store that contains the last request times to
+ * hidden service directories for certain queries. Specifically:
+ *
+ * key = base32(hsdir_identity) + base32(hs_identity)
+ * value = time_t of last request for that hs_identity to that HSDir
+ *
+ * where 'hsdir_identity' is the identity digest of the HSDir node, and
+ * 'hs_identity' is the descriptor ID of the HS in the v2 case, or the ed25519
+ * identity public key of the HS in the v3 case. */
+static strmap_t *last_hid_serv_requests_ = NULL;
+
+/** Returns last_hid_serv_requests_, initializing it to a new strmap if
+ * necessary. */
+STATIC strmap_t *
+get_last_hid_serv_requests(void)
+{
+ if (!last_hid_serv_requests_)
+ last_hid_serv_requests_ = strmap_new();
+ return last_hid_serv_requests_;
+}
+
+/** Look up the last request time to hidden service directory <b>hs_dir</b>
+ * for descriptor request key <b>req_key_str</b> which is the descriptor ID
+ * for a v2 service or the blinded key for v3. If <b>set</b> is non-zero,
+ * assign the current time <b>now</b> and return that. Otherwise, return the
+ * most recent request time, or 0 if no such request has been sent before. */
+time_t
+hs_lookup_last_hid_serv_request(routerstatus_t *hs_dir,
+ const char *req_key_str,
+ time_t now, int set)
+{
+ char hsdir_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
+ char *hsdir_desc_comb_id = NULL;
+ time_t *last_request_ptr;
+ strmap_t *last_hid_serv_requests = get_last_hid_serv_requests();
+
+ /* Create the key */
+ base32_encode(hsdir_id_base32, sizeof(hsdir_id_base32),
+ hs_dir->identity_digest, DIGEST_LEN);
+ tor_asprintf(&hsdir_desc_comb_id, "%s%s", hsdir_id_base32, req_key_str);
+
+ if (set) {
+ time_t *oldptr;
+ last_request_ptr = tor_malloc_zero(sizeof(time_t));
+ *last_request_ptr = now;
+ oldptr = strmap_set(last_hid_serv_requests, hsdir_desc_comb_id,
+ last_request_ptr);
+ tor_free(oldptr);
+ } else {
+ last_request_ptr = strmap_get(last_hid_serv_requests,
+ hsdir_desc_comb_id);
+ }
+
+ tor_free(hsdir_desc_comb_id);
+ return (last_request_ptr) ? *last_request_ptr : 0;
+}
+
+/** Clean the history of request times to hidden service directories, so that
+ * it does not contain requests older than REND_HID_SERV_DIR_REQUERY_PERIOD
+ * seconds any more. */
+void
+hs_clean_last_hid_serv_requests(time_t now)
+{
+ strmap_iter_t *iter;
+ time_t cutoff = now - hs_hsdir_requery_period(get_options());
+ strmap_t *last_hid_serv_requests = get_last_hid_serv_requests();
+ for (iter = strmap_iter_init(last_hid_serv_requests);
+ !strmap_iter_done(iter); ) {
+ const char *key;
+ void *val;
+ time_t *ent;
+ strmap_iter_get(iter, &key, &val);
+ ent = (time_t *) val;
+ if (*ent < cutoff) {
+ iter = strmap_iter_next_rmv(last_hid_serv_requests, iter);
+ tor_free(ent);
+ } else {
+ iter = strmap_iter_next(last_hid_serv_requests, iter);
+ }
+ }
+}
+
+/** Remove all requests related to the descriptor request key string
+ * <b>req_key_str</b> from the history of times of requests to hidden service
+ * directories.
+ *
+ * This is called from rend_client_note_connection_attempt_ended(), which
+ * must be idempotent, so any future changes to this function must leave it
+ * idempotent too. */
+void
+hs_purge_hid_serv_from_last_hid_serv_requests(const char *req_key_str)
+{
+ strmap_iter_t *iter;
+ strmap_t *last_hid_serv_requests = get_last_hid_serv_requests();
+
+ for (iter = strmap_iter_init(last_hid_serv_requests);
+ !strmap_iter_done(iter); ) {
+ const char *key;
+ void *val;
+ strmap_iter_get(iter, &key, &val);
+
+ /* XXX: The use of REND_DESC_ID_V2_LEN_BASE32 is very wrong in terms of
+ * semantic, see #23305. */
+
+ /* Length check on the strings we are about to compare. The "key" contains
+ * both the base32 HSDir identity digest and the requested key at the
+ * directory. The "req_key_str" can either be a base32 descriptor ID or a
+ * base64 blinded key which should be the second part of "key". BUG on
+ * this check because both strings are internally controlled so this
+ * should never happen. */
+ if (BUG((strlen(req_key_str) + REND_DESC_ID_V2_LEN_BASE32) <
+ strlen(key))) {
+ iter = strmap_iter_next(last_hid_serv_requests, iter);
+ continue;
+ }
+
+ /* Check if the tracked request matches our request key */
+ if (tor_memeq(key + REND_DESC_ID_V2_LEN_BASE32, req_key_str,
+ strlen(req_key_str))) {
+ iter = strmap_iter_next_rmv(last_hid_serv_requests, iter);
+ tor_free(val);
+ } else {
+ iter = strmap_iter_next(last_hid_serv_requests, iter);
+ }
+ }
+}
+
+/** Purge the history of request times to hidden service directories,
+ * so that future lookups of an HS descriptor will not fail because we
+ * accessed all of the HSDir relays responsible for the descriptor
+ * recently. */
+void
+hs_purge_last_hid_serv_requests(void)
+{
+ /* Don't create the table if it doesn't exist yet (and it may very
+ * well not exist if the user hasn't accessed any HSes)... */
+ strmap_t *old_last_hid_serv_requests = last_hid_serv_requests_;
+ /* ... and let get_last_hid_serv_requests re-create it for us if
+ * necessary. */
+ last_hid_serv_requests_ = NULL;
+
+ if (old_last_hid_serv_requests != NULL) {
+ log_info(LD_REND, "Purging client last-HS-desc-request-time table");
+ strmap_free(old_last_hid_serv_requests, tor_free_);
+ }
+}
+
+/***********************************************************************/
+
+/** Given the list of responsible HSDirs in <b>responsible_dirs</b>, pick the
+ * one that we should use to fetch a descriptor right now. Take into account
+ * previous failed attempts at fetching this descriptor from HSDirs using the
+ * string identifier <b>req_key_str</b>.
+ *
+ * Steals ownership of <b>responsible_dirs</b>.
+ *
+ * Return the routerstatus of the chosen HSDir if successful, otherwise return
+ * NULL if no HSDirs are worth trying right now. */
+routerstatus_t *
+hs_pick_hsdir(smartlist_t *responsible_dirs, const char *req_key_str)
+{
+ smartlist_t *usable_responsible_dirs = smartlist_new();
+ const or_options_t *options = get_options();
+ routerstatus_t *hs_dir;
+ time_t now = time(NULL);
+ int excluded_some;
+
+ tor_assert(req_key_str);
+
+ /* Clean outdated request history first. */
+ hs_clean_last_hid_serv_requests(now);
+
+ /* Only select those hidden service directories to which we did not send a
+ * request recently and for which we have a router descriptor here. */
+ SMARTLIST_FOREACH_BEGIN(responsible_dirs, routerstatus_t *, dir) {
+ time_t last = hs_lookup_last_hid_serv_request(dir, req_key_str, 0, 0);
+ const node_t *node = node_get_by_id(dir->identity_digest);
+ if (last + hs_hsdir_requery_period(options) >= now ||
+ !node || !node_has_descriptor(node)) {
+ SMARTLIST_DEL_CURRENT(responsible_dirs, dir);
+ continue;
+ }
+ if (!routerset_contains_node(options->ExcludeNodes, node)) {
+ smartlist_add(usable_responsible_dirs, dir);
+ }
+ } SMARTLIST_FOREACH_END(dir);
+
+ excluded_some =
+ smartlist_len(usable_responsible_dirs) < smartlist_len(responsible_dirs);
+
+ hs_dir = smartlist_choose(usable_responsible_dirs);
+ if (!hs_dir && !options->StrictNodes) {
+ hs_dir = smartlist_choose(responsible_dirs);
+ }
+
+ smartlist_free(responsible_dirs);
+ smartlist_free(usable_responsible_dirs);
+ if (!hs_dir) {
+ log_info(LD_REND, "Could not pick one of the responsible hidden "
+ "service directories, because we requested them all "
+ "recently without success.");
+ if (options->StrictNodes && excluded_some) {
+ log_warn(LD_REND, "Could not pick a hidden service directory for the "
+ "requested hidden service: they are all either down or "
+ "excluded, and StrictNodes is set.");
+ }
+ } else {
+ /* Remember that we are requesting a descriptor from this hidden service
+ * directory now. */
+ hs_lookup_last_hid_serv_request(hs_dir, req_key_str, now, 1);
+ }
+
+ return hs_dir;
+}
+
+/* From a list of link specifier, an onion key and if we are requesting a
+ * direct connection (ex: single onion service), return a newly allocated
+ * extend_info_t object. This function checks the firewall policies and if we
+ * are allowed to extend to the chosen address.
+ *
+ * if either IPv4 or legacy ID is missing, error.
+ * if not direct_conn, IPv4 is prefered.
+ * if direct_conn, IPv6 is prefered if we have one available.
+ * if firewall does not allow the chosen address, error.
+ *
+ * Return NULL if we can fulfill the conditions. */
+extend_info_t *
+hs_get_extend_info_from_lspecs(const smartlist_t *lspecs,
+ const curve25519_public_key_t *onion_key,
+ int direct_conn)
+{
+ int have_v4 = 0, have_v6 = 0, have_legacy_id = 0, have_ed25519_id = 0;
+ char legacy_id[DIGEST_LEN] = {0};
+ uint16_t port_v4 = 0, port_v6 = 0, port = 0;
+ tor_addr_t addr_v4, addr_v6, *addr = NULL;
+ ed25519_public_key_t ed25519_pk;
+ extend_info_t *info = NULL;
+
+ tor_assert(lspecs);
+
+ SMARTLIST_FOREACH_BEGIN(lspecs, const link_specifier_t *, ls) {
+ switch (link_specifier_get_ls_type(ls)) {
+ case LS_IPV4:
+ /* Skip if we already seen a v4. */
+ if (have_v4) continue;
+ tor_addr_from_ipv4h(&addr_v4,
+ link_specifier_get_un_ipv4_addr(ls));
+ port_v4 = link_specifier_get_un_ipv4_port(ls);
+ have_v4 = 1;
+ break;
+ case LS_IPV6:
+ /* Skip if we already seen a v6. */
+ if (have_v6) continue;
+ tor_addr_from_ipv6_bytes(&addr_v6,
+ (const char *) link_specifier_getconstarray_un_ipv6_addr(ls));
+ port_v6 = link_specifier_get_un_ipv6_port(ls);
+ have_v6 = 1;
+ break;
+ case LS_LEGACY_ID:
+ /* Make sure we do have enough bytes for the legacy ID. */
+ if (link_specifier_getlen_un_legacy_id(ls) < sizeof(legacy_id)) {
+ break;
+ }
+ memcpy(legacy_id, link_specifier_getconstarray_un_legacy_id(ls),
+ sizeof(legacy_id));
+ have_legacy_id = 1;
+ break;
+ case LS_ED25519_ID:
+ memcpy(ed25519_pk.pubkey,
+ link_specifier_getconstarray_un_ed25519_id(ls),
+ ED25519_PUBKEY_LEN);
+ have_ed25519_id = 1;
+ break;
+ default:
+ /* Ignore unknown. */
+ break;
+ }
+ } SMARTLIST_FOREACH_END(ls);
+
+ /* IPv4 and legacy ID are mandatory. */
+ if (!have_v4 || !have_legacy_id) {
+ goto done;
+ }
+ /* By default, we pick IPv4 but this might change to v6 if certain
+ * conditions are met. */
+ addr = &addr_v4; port = port_v4;
+
+ /* If we are NOT in a direct connection, we'll use our Guard and a 3-hop
+ * circuit so we can't extend in IPv6. And at this point, we do have an IPv4
+ * address available so go to validation. */
+ if (!direct_conn) {
+ goto validate;
+ }
+
+ /* From this point on, we have a request for a direct connection to the
+ * rendezvous point so make sure we can actually connect through our
+ * firewall. We'll prefer IPv6. */
+
+ /* IPv6 test. */
+ if (have_v6 &&
+ fascist_firewall_allows_address_addr(&addr_v6, port_v6,
+ FIREWALL_OR_CONNECTION, 1, 1)) {
+ /* Direct connection and we can reach it in IPv6 so go for it. */
+ addr = &addr_v6; port = port_v6;
+ goto validate;
+ }
+ /* IPv4 test and we are sure we have a v4 because of the check above. */
+ if (fascist_firewall_allows_address_addr(&addr_v4, port_v4,
+ FIREWALL_OR_CONNECTION, 0, 0)) {
+ /* Direct connection and we can reach it in IPv4 so go for it. */
+ addr = &addr_v4; port = port_v4;
+ goto validate;
+ }
+
+ validate:
+ /* We'll validate now that the address we've picked isn't a private one. If
+ * it is, are we allowing to extend to private address? */
+ if (!extend_info_addr_is_allowed(addr)) {
+ log_warn(LD_REND, "Requested address is private and it is not "
+ "allowed to extend to it: %s:%u",
+ fmt_addr(&addr_v4), port_v4);
+ goto done;
+ }
+
+ /* We do have everything for which we think we can connect successfully. */
+ info = extend_info_new(NULL, legacy_id,
+ (have_ed25519_id) ? &ed25519_pk : NULL, NULL,
+ onion_key, addr, port);
+ done:
+ return info;
+}
+
+/***********************************************************************/
+
+/* Initialize the entire HS subsytem. This is called in tor_init() before any
+ * torrc options are loaded. Only for >= v3. */
+void
+hs_init(void)
+{
+ hs_circuitmap_init();
+ hs_service_init();
+ hs_cache_init();
+}
+
+/* Release and cleanup all memory of the HS subsystem (all version). This is
+ * called by tor_free_all(). */
+void
+hs_free_all(void)
+{
+ hs_circuitmap_free_all();
+ hs_service_free_all();
+ hs_cache_free_all();
+}
+
+/* For the given origin circuit circ, decrement the number of rendezvous
+ * stream counter. This handles every hidden service version. */
+void
+hs_dec_rdv_stream_counter(origin_circuit_t *circ)
+{
+ tor_assert(circ);
+
+ if (circ->rend_data) {
+ circ->rend_data->nr_streams--;
+ } else if (circ->hs_ident) {
+ circ->hs_ident->num_rdv_streams--;
+ } else {
+ /* Should not be called if this circuit is not for hidden service. */
+ tor_assert_nonfatal_unreached();
+ }
+}
+
+/* For the given origin circuit circ, increment the number of rendezvous
+ * stream counter. This handles every hidden service version. */
+void
+hs_inc_rdv_stream_counter(origin_circuit_t *circ)
+{
+ tor_assert(circ);
+
+ if (circ->rend_data) {
+ circ->rend_data->nr_streams++;
+ } else if (circ->hs_ident) {
+ circ->hs_ident->num_rdv_streams++;
+ } else {
+ /* Should not be called if this circuit is not for hidden service. */
+ tor_assert_nonfatal_unreached();
+ }
+}
+
diff --git a/src/or/hs_common.h b/src/or/hs_common.h
index a8fded652a..79d92d915f 100644
--- a/src/or/hs_common.h
+++ b/src/or/hs_common.h
@@ -11,15 +11,21 @@
#include "or.h"
+/* Trunnel */
+#include "ed25519_cert.h"
+
/* Protocol version 2. Use this instead of hardcoding "2" in the code base,
* this adds a clearer semantic to the value when used. */
#define HS_VERSION_TWO 2
/* Version 3 of the protocol (prop224). */
#define HS_VERSION_THREE 3
+/* Earliest and latest version we support. */
+#define HS_VERSION_MIN HS_VERSION_TWO
+#define HS_VERSION_MAX HS_VERSION_THREE
/** Try to maintain this many intro points per service by default. */
#define NUM_INTRO_POINTS_DEFAULT 3
-/** Maximum number of intro points per service. */
+/** Maximum number of intro points per generic and version 2 service. */
#define NUM_INTRO_POINTS_MAX 10
/** Number of extra intro points we launch if our set of intro nodes is empty.
* See proposal 155, section 4. */
@@ -46,12 +52,124 @@
#define HS_TIME_PERIOD_LENGTH_MIN 30 /* minutes */
/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */
#define HS_TIME_PERIOD_LENGTH_MAX (60 * 24 * 10) /* 10 days or 14400 minutes */
+
+/* Prefix of the onion address checksum. */
+#define HS_SERVICE_ADDR_CHECKSUM_PREFIX ".onion checksum"
+/* Length of the checksum prefix minus the NUL terminated byte. */
+#define HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN \
+ (sizeof(HS_SERVICE_ADDR_CHECKSUM_PREFIX) - 1)
+/* Length of the resulting checksum of the address. The construction of this
+ * checksum looks like:
+ * CHECKSUM = ".onion checksum" || PUBKEY || VERSION
+ * where VERSION is 1 byte. This is pre-hashing. */
+#define HS_SERVICE_ADDR_CHECKSUM_INPUT_LEN \
+ (HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN + ED25519_PUBKEY_LEN + sizeof(uint8_t))
+/* The amount of bytes we use from the address checksum. */
+#define HS_SERVICE_ADDR_CHECKSUM_LEN_USED 2
+/* Length of the binary encoded service address which is of course before the
+ * base32 encoding. Construction is:
+ * PUBKEY || CHECKSUM || VERSION
+ * with 1 byte VERSION and 2 bytes CHECKSUM. The following is 35 bytes. */
+#define HS_SERVICE_ADDR_LEN \
+ (ED25519_PUBKEY_LEN + HS_SERVICE_ADDR_CHECKSUM_LEN_USED + sizeof(uint8_t))
+/* Length of 'y' portion of 'y.onion' URL. This is base32 encoded and the
+ * length ends up to 56 bytes (not counting the terminated NUL byte.) */
+#define HS_SERVICE_ADDR_LEN_BASE32 \
+ (CEIL_DIV(HS_SERVICE_ADDR_LEN * 8, 5))
+
+/* The default HS time period length */
+#define HS_TIME_PERIOD_LENGTH_DEFAULT 1440 /* 1440 minutes == one day */
+/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */
+#define HS_TIME_PERIOD_LENGTH_MIN 30 /* minutes */
+/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */
+#define HS_TIME_PERIOD_LENGTH_MAX (60 * 24 * 10) /* 10 days or 14400 minutes */
/* The time period rotation offset as seen in prop224 section [TIME-PERIODS] */
#define HS_TIME_PERIOD_ROTATION_OFFSET (12 * 60) /* minutes */
+/* Keyblinding parameter construction is as follow:
+ * "key-blind" || INT_8(period_num) || INT_8(start_period_sec) */
+#define HS_KEYBLIND_NONCE_PREFIX "key-blind"
+#define HS_KEYBLIND_NONCE_PREFIX_LEN (sizeof(HS_KEYBLIND_NONCE_PREFIX) - 1)
+#define HS_KEYBLIND_NONCE_LEN \
+ (HS_KEYBLIND_NONCE_PREFIX_LEN + sizeof(uint64_t) + sizeof(uint64_t))
+
+/* Credential and subcredential prefix value. */
+#define HS_CREDENTIAL_PREFIX "credential"
+#define HS_CREDENTIAL_PREFIX_LEN (sizeof(HS_CREDENTIAL_PREFIX) - 1)
+#define HS_SUBCREDENTIAL_PREFIX "subcredential"
+#define HS_SUBCREDENTIAL_PREFIX_LEN (sizeof(HS_SUBCREDENTIAL_PREFIX) - 1)
+
+/* Node hidden service stored at index prefix value. */
+#define HS_INDEX_PREFIX "store-at-idx"
+#define HS_INDEX_PREFIX_LEN (sizeof(HS_INDEX_PREFIX) - 1)
+
+/* Node hidden service directory index prefix value. */
+#define HSDIR_INDEX_PREFIX "node-idx"
+#define HSDIR_INDEX_PREFIX_LEN (sizeof(HSDIR_INDEX_PREFIX) - 1)
+
+/* Prefix of the shared random value disaster mode. */
+#define HS_SRV_DISASTER_PREFIX "shared-random-disaster"
+#define HS_SRV_DISASTER_PREFIX_LEN (sizeof(HS_SRV_DISASTER_PREFIX) - 1)
+
+/* Default value of number of hsdir replicas (hsdir_n_replicas). */
+#define HS_DEFAULT_HSDIR_N_REPLICAS 2
+/* Default value of hsdir spread store (hsdir_spread_store). */
+#define HS_DEFAULT_HSDIR_SPREAD_STORE 3
+/* Default value of hsdir spread fetch (hsdir_spread_fetch). */
+#define HS_DEFAULT_HSDIR_SPREAD_FETCH 3
+
+/* Type of authentication key used by an introduction point. */
+typedef enum {
+ HS_AUTH_KEY_TYPE_LEGACY = 1,
+ HS_AUTH_KEY_TYPE_ED25519 = 2,
+} hs_auth_key_type_t;
+
+/* Represents the mapping from a virtual port of a rendezvous service to a
+ * real port on some IP. */
+typedef struct rend_service_port_config_t {
+ /* The incoming HS virtual port we're mapping */
+ uint16_t virtual_port;
+ /* Is this an AF_UNIX port? */
+ unsigned int is_unix_addr:1;
+ /* The outgoing TCP port to use, if !is_unix_addr */
+ uint16_t real_port;
+ /* The outgoing IPv4 or IPv6 address to use, if !is_unix_addr */
+ tor_addr_t real_addr;
+ /* The socket path to connect to, if is_unix_addr */
+ char unix_addr[FLEXIBLE_ARRAY_MEMBER];
+} rend_service_port_config_t;
+
+/* Hidden service directory index used in a node_t which is set once we set
+ * the consensus. */
+typedef struct hsdir_index_t {
+ /* The hsdir index for the current time period. */
+ uint8_t current[DIGEST256_LEN];
+ /* The hsdir index for the next time period. */
+ uint8_t next[DIGEST256_LEN];
+} hsdir_index_t;
+
+void hs_init(void);
+void hs_free_all(void);
+
int hs_check_service_private_dir(const char *username, const char *path,
unsigned int dir_group_readable,
unsigned int create);
+char *hs_path_from_filename(const char *directory, const char *filename);
+void hs_build_address(const ed25519_public_key_t *key, uint8_t version,
+ char *addr_out);
+int hs_address_is_valid(const char *address);
+int hs_parse_address(const char *address, ed25519_public_key_t *key_out,
+ uint8_t *checksum_out, uint8_t *version_out);
+
+void hs_build_blinded_pubkey(const ed25519_public_key_t *pubkey,
+ const uint8_t *secret, size_t secret_len,
+ uint64_t time_period_num,
+ ed25519_public_key_t *pubkey_out);
+void hs_build_blinded_keypair(const ed25519_keypair_t *kp,
+ const uint8_t *secret, size_t secret_len,
+ uint64_t time_period_num,
+ ed25519_keypair_t *kp_out);
+int hs_service_requires_uptime_circ(const smartlist_t *ports);
void rend_data_free(rend_data_t *data);
rend_data_t *rend_data_dup(const rend_data_t *data);
@@ -69,14 +187,78 @@ const char *rend_data_get_desc_id(const rend_data_t *rend_data,
const uint8_t *rend_data_get_pk_digest(const rend_data_t *rend_data,
size_t *len_out);
+routerstatus_t *pick_hsdir(const char *desc_id, const char *desc_id_base32);
+
+void hs_get_subcredential(const ed25519_public_key_t *identity_pk,
+ const ed25519_public_key_t *blinded_pk,
+ uint8_t *subcred_out);
+
+uint64_t hs_get_time_period_num(time_t now);
uint64_t hs_get_next_time_period_num(time_t now);
+time_t hs_get_start_time_of_next_time_period(time_t now);
+
+link_specifier_t *hs_link_specifier_dup(const link_specifier_t *lspec);
+
+MOCK_DECL(int, hs_overlap_mode_is_active,
+ (const networkstatus_t *consensus, time_t now));
+
+uint8_t *hs_get_current_srv(uint64_t time_period_num,
+ const networkstatus_t *ns);
+uint8_t *hs_get_previous_srv(uint64_t time_period_num,
+ const networkstatus_t *ns);
+
+void hs_build_hsdir_index(const ed25519_public_key_t *identity_pk,
+ const uint8_t *srv, uint64_t period_num,
+ uint8_t *hsdir_index_out);
+void hs_build_hs_index(uint64_t replica,
+ const ed25519_public_key_t *blinded_pk,
+ uint64_t period_num, uint8_t *hs_index_out);
+
+int32_t hs_get_hsdir_n_replicas(void);
+int32_t hs_get_hsdir_spread_fetch(void);
+int32_t hs_get_hsdir_spread_store(void);
+
+void hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk,
+ uint64_t time_period_num, int is_next_period,
+ int is_client, smartlist_t *responsible_dirs);
+routerstatus_t *hs_pick_hsdir(smartlist_t *responsible_dirs,
+ const char *req_key_str);
+
+time_t hs_hsdir_requery_period(const or_options_t *options);
+time_t hs_lookup_last_hid_serv_request(routerstatus_t *hs_dir,
+ const char *desc_id_base32,
+ time_t now, int set);
+void hs_clean_last_hid_serv_requests(time_t now);
+void hs_purge_hid_serv_from_last_hid_serv_requests(const char *desc_id);
+void hs_purge_last_hid_serv_requests(void);
+
+int hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn);
+
+void hs_inc_rdv_stream_counter(origin_circuit_t *circ);
+void hs_dec_rdv_stream_counter(origin_circuit_t *circ);
+
+extend_info_t *hs_get_extend_info_from_lspecs(const smartlist_t *lspecs,
+ const curve25519_public_key_t *onion_key,
+ int direct_conn);
#ifdef HS_COMMON_PRIVATE
+STATIC void get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out);
+
+/** The period for which a hidden service directory cannot be queried for
+ * the same descriptor ID again. */
+#define REND_HID_SERV_DIR_REQUERY_PERIOD (15 * 60)
+/** Test networks generate a new consensus every 5 or 10 seconds.
+ * So allow them to requery HSDirs much faster. */
+#define REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING (5)
+
#ifdef TOR_UNIT_TESTS
+STATIC strmap_t *get_last_hid_serv_requests(void);
STATIC uint64_t get_time_period_length(void);
-STATIC uint64_t get_time_period_num(time_t now);
+
+STATIC uint8_t *get_first_cached_disaster_srv(void);
+STATIC uint8_t *get_second_cached_disaster_srv(void);
#endif /* TOR_UNIT_TESTS */
diff --git a/src/or/hs_config.c b/src/or/hs_config.c
new file mode 100644
index 0000000000..5f9282ea79
--- /dev/null
+++ b/src/or/hs_config.c
@@ -0,0 +1,582 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_config.c
+ * \brief Implement hidden service configuration subsystem.
+ *
+ * \details
+ *
+ * This file has basically one main entry point: hs_config_service_all(). It
+ * takes the torrc options and configure hidden service from it. In validate
+ * mode, nothing is added to the global service list or keys are not generated
+ * nor loaded.
+ *
+ * A service is configured in two steps. It is first created using the tor
+ * options and then put in a staging list. It will stay there until
+ * hs_service_load_all_keys() is called. That function is responsible to
+ * load/generate the keys for the service in the staging list and if
+ * successful, transfert the service to the main global service list where
+ * at that point it is ready to be used.
+ *
+ * Configuration functions are per-version and there is a main generic one for
+ * every option that is common to all version (config_generic_service).
+ **/
+
+#define HS_CONFIG_PRIVATE
+
+#include "hs_common.h"
+#include "hs_config.h"
+#include "hs_service.h"
+#include "rendservice.h"
+
+/* Using the given list of services, stage them into our global state. Every
+ * service version are handled. This function can remove entries in the given
+ * service_list.
+ *
+ * Staging a service means that we take all services in service_list and we
+ * put them in the staging list (global) which acts as a temporary list that
+ * is used by the service loading key process. In other words, staging a
+ * service puts it in a list to be considered when loading the keys and then
+ * moved to the main global list. */
+static void
+stage_services(smartlist_t *service_list)
+{
+ tor_assert(service_list);
+
+ /* This is v2 specific. Trigger service pruning which will make sure the
+ * just configured services end up in the main global list. It should only
+ * be done in non validation mode because v2 subsystem handles service
+ * object differently. */
+ rend_service_prune_list();
+
+ /* Cleanup v2 service from the list, we don't need those object anymore
+ * because we validated them all against the others and we want to stage
+ * only >= v3 service. And remember, v2 has a different object type which is
+ * shadow copied from an hs_service_t type. */
+ SMARTLIST_FOREACH_BEGIN(service_list, hs_service_t *, s) {
+ if (s->config.version == HS_VERSION_TWO) {
+ SMARTLIST_DEL_CURRENT(service_list, s);
+ hs_service_free(s);
+ }
+ } SMARTLIST_FOREACH_END(s);
+
+ /* This is >= v3 specific. Using the newly configured service list, stage
+ * them into our global state. Every object ownership is lost after. */
+ hs_service_stage_services(service_list);
+}
+
+/* Validate the given service against all service in the given list. If the
+ * service is ephemeral, this function ignores it. Services with the same
+ * directory path aren't allowed and will return an error. If a duplicate is
+ * found, 1 is returned else 0 if none found. */
+static int
+service_is_duplicate_in_list(const smartlist_t *service_list,
+ const hs_service_t *service)
+{
+ int ret = 0;
+
+ tor_assert(service_list);
+ tor_assert(service);
+
+ /* Ephemeral service don't have a directory configured so no need to check
+ * for a service in the list having the same path. */
+ if (service->config.is_ephemeral) {
+ goto end;
+ }
+
+ /* XXX: Validate if we have any service that has the given service dir path.
+ * This has two problems:
+ *
+ * a) It's O(n^2), but the same comment from the bottom of
+ * rend_config_services() should apply.
+ *
+ * b) We only compare directory paths as strings, so we can't
+ * detect two distinct paths that specify the same directory
+ * (which can arise from symlinks, case-insensitivity, bind
+ * mounts, etc.).
+ *
+ * It also can't detect that two separate Tor instances are trying
+ * to use the same HiddenServiceDir; for that, we would need a
+ * lock file. But this is enough to detect a simple mistake that
+ * at least one person has actually made. */
+ SMARTLIST_FOREACH_BEGIN(service_list, const hs_service_t *, s) {
+ if (!strcmp(s->config.directory_path, service->config.directory_path)) {
+ log_warn(LD_REND, "Another hidden service is already configured "
+ "for directory %s",
+ escaped(service->config.directory_path));
+ ret = 1;
+ goto end;
+ }
+ } SMARTLIST_FOREACH_END(s);
+
+ end:
+ return ret;
+}
+
+/* Helper function: Given an configuration option name, its value, a minimum
+ * min and a maxium max, parse the value as a uint64_t. On success, ok is set
+ * to 1 and ret is the parsed value. On error, ok is set to 0 and ret must be
+ * ignored. This function logs both on error and success. */
+static uint64_t
+helper_parse_uint64(const char *opt, const char *value, uint64_t min,
+ uint64_t max, int *ok)
+{
+ uint64_t ret = 0;
+
+ tor_assert(opt);
+ tor_assert(value);
+ tor_assert(ok);
+
+ *ok = 0;
+ ret = tor_parse_uint64(value, 10, min, max, ok, NULL);
+ if (!*ok) {
+ log_warn(LD_CONFIG, "%s must be between %" PRIu64 " and %"PRIu64
+ ", not %s.",
+ opt, min, max, value);
+ goto err;
+ }
+ log_info(LD_CONFIG, "%s was parsed to %" PRIu64, opt, ret);
+ err:
+ return ret;
+}
+
+/* Return true iff the given options starting at line_ for a hidden service
+ * contains at least one invalid option. Each hidden service option don't
+ * apply to all versions so this function can find out. The line_ MUST start
+ * right after the HiddenServiceDir line of this service.
+ *
+ * This is mainly for usability so we can inform the user of any invalid
+ * option for the hidden service version instead of silently ignoring. */
+static int
+config_has_invalid_options(const config_line_t *line_,
+ const hs_service_t *service)
+{
+ int ret = 0;
+ const char **optlist;
+ const config_line_t *line;
+
+ tor_assert(service);
+ tor_assert(service->config.version <= HS_VERSION_MAX);
+
+ /* List of options that a v3 service doesn't support thus must exclude from
+ * its configuration. */
+ const char *opts_exclude_v3[] = {
+ "HiddenServiceAuthorizeClient",
+ NULL /* End marker. */
+ };
+
+ /* Defining the size explicitly allows us to take advantage of the compiler
+ * which warns us if we ever bump the max version but forget to grow this
+ * array. The plus one is because we have a version 0 :). */
+ struct {
+ const char **list;
+ } exclude_lists[HS_VERSION_MAX + 1] = {
+ { NULL }, /* v0. */
+ { NULL }, /* v1. */
+ { NULL }, /* v2 */
+ { opts_exclude_v3 }, /* v3. */
+ };
+
+ optlist = exclude_lists[service->config.version].list;
+ if (optlist == NULL) {
+ /* No exclude options to look at for this version. */
+ goto end;
+ }
+ for (int i = 0; optlist[i]; i++) {
+ const char *opt = optlist[i];
+ for (line = line_; line; line = line->next) {
+ if (!strcasecmp(line->key, "HiddenServiceDir")) {
+ /* We just hit the next hidden service, stop right now. */
+ goto end;
+ }
+ if (!strcasecmp(line->key, opt)) {
+ log_warn(LD_CONFIG, "Hidden service option %s is incompatible with "
+ "version %" PRIu32 " of service in %s",
+ opt, service->config.version,
+ service->config.directory_path);
+ ret = 1;
+ /* Continue the loop so we can find all possible options. */
+ continue;
+ }
+ }
+ }
+ end:
+ return ret;
+}
+
+/* Validate service configuration. This is used when loading the configuration
+ * and once we've setup a service object, it's config object is passed to this
+ * function for further validation. This does not validate service key
+ * material. Return 0 if valid else -1 if invalid. */
+static int
+config_validate_service(const hs_service_config_t *config)
+{
+ tor_assert(config);
+
+ /* Amount of ports validation. */
+ if (!config->ports || smartlist_len(config->ports) == 0) {
+ log_warn(LD_CONFIG, "Hidden service (%s) with no ports configured.",
+ escaped(config->directory_path));
+ goto invalid;
+ }
+
+ /* Valid. */
+ return 0;
+ invalid:
+ return -1;
+}
+
+/* Configuration funcion for a version 3 service. The line_ must be pointing
+ * to the directive directly after a HiddenServiceDir. That way, when hitting
+ * the next HiddenServiceDir line or reaching the end of the list of lines, we
+ * know that we have to stop looking for more options. The given service
+ * object must be already allocated and passed through
+ * config_generic_service() prior to calling this function.
+ *
+ * Return 0 on success else a negative value. */
+static int
+config_service_v3(const config_line_t *line_,
+ hs_service_config_t *config)
+{
+ int have_num_ip = 0;
+ const char *dup_opt_seen = NULL;
+ const config_line_t *line;
+
+ tor_assert(config);
+
+ for (line = line_; line; line = line->next) {
+ int ok = 0;
+ if (!strcasecmp(line->key, "HiddenServiceDir")) {
+ /* We just hit the next hidden service, stop right now. */
+ break;
+ }
+ /* Number of introduction points. */
+ if (!strcasecmp(line->key, "HiddenServiceNumIntroductionPoints")) {
+ config->num_intro_points =
+ (unsigned int) helper_parse_uint64(line->key, line->value,
+ NUM_INTRO_POINTS_DEFAULT,
+ HS_CONFIG_V3_MAX_INTRO_POINTS,
+ &ok);
+ if (!ok || have_num_ip) {
+ if (have_num_ip)
+ dup_opt_seen = line->key;
+ goto err;
+ }
+ have_num_ip = 1;
+ continue;
+ }
+ }
+
+ /* We do not load the key material for the service at this stage. This is
+ * done later once tor can confirm that it is in a running state. */
+
+ /* We are about to return a fully configured service so do one last pass of
+ * validation at it. */
+ if (config_validate_service(config) < 0) {
+ goto err;
+ }
+
+ return 0;
+ err:
+ if (dup_opt_seen) {
+ log_warn(LD_CONFIG, "Duplicate directive %s.", dup_opt_seen);
+ }
+ return -1;
+}
+
+/* Configure a service using the given options in line_ and options. This is
+ * called for any service regardless of its version which means that all
+ * directives in this function are generic to any service version. This
+ * function will also check the validity of the service directory path.
+ *
+ * The line_ must be pointing to the directive directly after a
+ * HiddenServiceDir. That way, when hitting the next HiddenServiceDir line or
+ * reaching the end of the list of lines, we know that we have to stop looking
+ * for more options.
+ *
+ * Return 0 on success else -1. */
+static int
+config_generic_service(const config_line_t *line_,
+ const or_options_t *options,
+ hs_service_t *service)
+{
+ int dir_seen = 0;
+ const config_line_t *line;
+ hs_service_config_t *config;
+ /* If this is set, we've seen a duplicate of this option. Keep the string
+ * so we can log the directive. */
+ const char *dup_opt_seen = NULL;
+ /* These variables will tell us if we ever have duplicate. */
+ int have_version = 0, have_allow_unknown_ports = 0;
+ int have_dir_group_read = 0, have_max_streams = 0;
+ int have_max_streams_close = 0;
+
+ tor_assert(line_);
+ tor_assert(options);
+ tor_assert(service);
+
+ /* Makes thing easier. */
+ config = &service->config;
+
+ /* The first line starts with HiddenServiceDir so we consider what's next is
+ * the configuration of the service. */
+ for (line = line_; line ; line = line->next) {
+ int ok = 0;
+
+ /* This indicate that we have a new service to configure. */
+ if (!strcasecmp(line->key, "HiddenServiceDir")) {
+ /* This function only configures one service at a time so if we've
+ * already seen one, stop right now. */
+ if (dir_seen) {
+ break;
+ }
+ /* Ok, we've seen one and we are about to configure it. */
+ dir_seen = 1;
+ config->directory_path = tor_strdup(line->value);
+ log_info(LD_CONFIG, "HiddenServiceDir=%s. Configuring...",
+ escaped(config->directory_path));
+ continue;
+ }
+ if (BUG(!dir_seen)) {
+ goto err;
+ }
+ /* Version of the service. */
+ if (!strcasecmp(line->key, "HiddenServiceVersion")) {
+ service->config.version =
+ (uint32_t) helper_parse_uint64(line->key, line->value, HS_VERSION_MIN,
+ HS_VERSION_MAX, &ok);
+ if (!ok || have_version) {
+ if (have_version)
+ dup_opt_seen = line->key;
+ goto err;
+ }
+ have_version = 1;
+ continue;
+ }
+ /* Virtual port. */
+ if (!strcasecmp(line->key, "HiddenServicePort")) {
+ char *err_msg = NULL;
+ /* XXX: Can we rename this? */
+ rend_service_port_config_t *portcfg =
+ rend_service_parse_port_config(line->value, " ", &err_msg);
+ if (!portcfg) {
+ if (err_msg) {
+ log_warn(LD_CONFIG, "%s", err_msg);
+ }
+ tor_free(err_msg);
+ goto err;
+ }
+ tor_assert(!err_msg);
+ smartlist_add(config->ports, portcfg);
+ log_info(LD_CONFIG, "HiddenServicePort=%s for %s",
+ line->value, escaped(config->directory_path));
+ continue;
+ }
+ /* Do we allow unknown ports. */
+ if (!strcasecmp(line->key, "HiddenServiceAllowUnknownPorts")) {
+ config->allow_unknown_ports =
+ (unsigned int) helper_parse_uint64(line->key, line->value, 0, 1, &ok);
+ if (!ok || have_allow_unknown_ports) {
+ if (have_allow_unknown_ports)
+ dup_opt_seen = line->key;
+ goto err;
+ }
+ have_allow_unknown_ports = 1;
+ continue;
+ }
+ /* Directory group readable. */
+ if (!strcasecmp(line->key, "HiddenServiceDirGroupReadable")) {
+ config->dir_group_readable =
+ (unsigned int) helper_parse_uint64(line->key, line->value, 0, 1, &ok);
+ if (!ok || have_dir_group_read) {
+ if (have_dir_group_read)
+ dup_opt_seen = line->key;
+ goto err;
+ }
+ have_dir_group_read = 1;
+ continue;
+ }
+ /* Maximum streams per circuit. */
+ if (!strcasecmp(line->key, "HiddenServiceMaxStreams")) {
+ config->max_streams_per_rdv_circuit =
+ helper_parse_uint64(line->key, line->value, 0,
+ HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT, &ok);
+ if (!ok || have_max_streams) {
+ if (have_max_streams)
+ dup_opt_seen = line->key;
+ goto err;
+ }
+ have_max_streams = 1;
+ continue;
+ }
+ /* Maximum amount of streams before we close the circuit. */
+ if (!strcasecmp(line->key, "HiddenServiceMaxStreamsCloseCircuit")) {
+ config->max_streams_close_circuit =
+ (unsigned int) helper_parse_uint64(line->key, line->value, 0, 1, &ok);
+ if (!ok || have_max_streams_close) {
+ if (have_max_streams_close)
+ dup_opt_seen = line->key;
+ goto err;
+ }
+ have_max_streams_close = 1;
+ continue;
+ }
+ }
+
+ /* Check if we are configured in non anonymous mode and single hop mode
+ * meaning every service become single onion. */
+ if (rend_service_allow_non_anonymous_connection(options) &&
+ rend_service_non_anonymous_mode_enabled(options)) {
+ config->is_single_onion = 1;
+ }
+
+ /* Success */
+ return 0;
+ err:
+ if (dup_opt_seen) {
+ log_warn(LD_CONFIG, "Duplicate directive %s.", dup_opt_seen);
+ }
+ return -1;
+}
+
+/* Configure a service using the given line and options. This function will
+ * call the corresponding configuration function for a specific service
+ * version and validate the service against the other ones. On success, add
+ * the service to the given list and return 0. On error, nothing is added to
+ * the list and a negative value is returned. */
+static int
+config_service(const config_line_t *line, const or_options_t *options,
+ smartlist_t *service_list)
+{
+ int ret;
+ hs_service_t *service = NULL;
+
+ tor_assert(line);
+ tor_assert(options);
+ tor_assert(service_list);
+
+ /* We have a new hidden service. */
+ service = hs_service_new(options);
+ /* We'll configure that service as a generic one and then pass it to a
+ * specific function according to the configured version number. */
+ if (config_generic_service(line, options, service) < 0) {
+ goto err;
+ }
+ tor_assert(service->config.version <= HS_VERSION_MAX);
+ /* Before we configure the service on a per-version basis, we'll make
+ * sure that this set of options for a service are valid that is for
+ * instance an option only for v2 is not used for v3. */
+ if (config_has_invalid_options(line->next, service)) {
+ goto err;
+ }
+ /* Check permission on service directory that was just parsed. And this must
+ * be done regardless of the service version. Do not ask for the directory
+ * to be created, this is done when the keys are loaded because we could be
+ * in validation mode right now. */
+ if (hs_check_service_private_dir(options->User,
+ service->config.directory_path,
+ service->config.dir_group_readable,
+ 0) < 0) {
+ goto err;
+ }
+ /* Different functions are in charge of specific options for a version. We
+ * start just after the service directory line so once we hit another
+ * directory line, the function knows that it has to stop parsing. */
+ switch (service->config.version) {
+ case HS_VERSION_TWO:
+ ret = rend_config_service(line->next, options, &service->config);
+ break;
+ case HS_VERSION_THREE:
+ ret = config_service_v3(line->next, &service->config);
+ break;
+ default:
+ /* We do validate before if we support the parsed version. */
+ tor_assert_nonfatal_unreached();
+ goto err;
+ }
+ if (ret < 0) {
+ goto err;
+ }
+ /* We'll check if this service can be kept depending on the others
+ * configured previously. */
+ if (service_is_duplicate_in_list(service_list, service)) {
+ goto err;
+ }
+ /* Passes, add it to the given list. */
+ smartlist_add(service_list, service);
+ return 0;
+
+ err:
+ hs_service_free(service);
+ return -1;
+}
+
+/* From a set of <b>options</b>, setup every hidden service found. Return 0 on
+ * success or -1 on failure. If <b>validate_only</b> is set, parse, warn and
+ * return as normal, but don't actually change the configured services. */
+int
+hs_config_service_all(const or_options_t *options, int validate_only)
+{
+ int dir_option_seen = 0, ret = -1;
+ const config_line_t *line;
+ smartlist_t *new_service_list = NULL;
+
+ tor_assert(options);
+
+ /* Newly configured service are put in that list which is then used for
+ * validation and staging for >= v3. */
+ new_service_list = smartlist_new();
+
+ for (line = options->RendConfigLines; line; line = line->next) {
+ /* Ignore all directives that aren't the start of a service. */
+ if (strcasecmp(line->key, "HiddenServiceDir")) {
+ if (!dir_option_seen) {
+ log_warn(LD_CONFIG, "%s with no preceding HiddenServiceDir directive",
+ line->key);
+ goto err;
+ }
+ continue;
+ }
+ /* Flag that we've seen a directory directive and we'll use it to make
+ * sure that the torrc options ordering is actually valid. */
+ dir_option_seen = 1;
+
+ /* Try to configure this service now. On success, it will be added to the
+ * list and validated against the service in that same list. */
+ if (config_service(line, options, new_service_list) < 0) {
+ goto err;
+ }
+ }
+
+ /* In non validation mode, we'll stage those services we just successfully
+ * configured. Service ownership is transfered from the list to the global
+ * state. If any service is invalid, it will be removed from the list and
+ * freed. All versions are handled in that function. */
+ if (!validate_only) {
+ stage_services(new_service_list);
+ } else {
+ /* We've just validated that we were able to build a clean working list of
+ * services. We don't need those objects anymore. */
+ SMARTLIST_FOREACH(new_service_list, hs_service_t *, s,
+ hs_service_free(s));
+ /* For the v2 subsystem, the configuration function adds the service
+ * object to the staging list and it is transferred in the main list
+ * through the prunning process. In validation mode, we thus have to purge
+ * the staging list so it's not kept in memory as valid service. */
+ rend_service_free_staging_list();
+ }
+
+ /* Success. Note that the service list has no ownership of its content. */
+ ret = 0;
+ goto end;
+
+ err:
+ SMARTLIST_FOREACH(new_service_list, hs_service_t *, s, hs_service_free(s));
+
+ end:
+ smartlist_free(new_service_list);
+ /* Tor main should call the free all function on error. */
+ return ret;
+}
+
diff --git a/src/or/hs_config.h b/src/or/hs_config.h
new file mode 100644
index 0000000000..2f8cbdc130
--- /dev/null
+++ b/src/or/hs_config.h
@@ -0,0 +1,24 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_config.h
+ * \brief Header file containing configuration ABI/API for the HS subsytem.
+ **/
+
+#ifndef TOR_HS_CONFIG_H
+#define TOR_HS_CONFIG_H
+
+#include "or.h"
+
+/* Max value for HiddenServiceMaxStreams */
+#define HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT 65535
+/* Maximum number of intro points per version 3 services. */
+#define HS_CONFIG_V3_MAX_INTRO_POINTS 20
+
+/* API */
+
+int hs_config_service_all(const or_options_t *options, int validate_only);
+
+#endif /* TOR_HS_CONFIG_H */
+
diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c
index 2a000f5002..4bc54bdb2d 100644
--- a/src/or/hs_descriptor.c
+++ b/src/or/hs_descriptor.c
@@ -55,13 +55,14 @@
/* For unit tests.*/
#define HS_DESCRIPTOR_PRIVATE
-#include "hs_descriptor.h"
-
#include "or.h"
#include "ed25519_cert.h" /* Trunnel interface. */
+#include "hs_descriptor.h"
+#include "circuitbuild.h"
#include "parsecommon.h"
#include "rendcache.h"
#include "hs_cache.h"
+#include "hs_config.h"
#include "torcert.h" /* tor_cert_encode_ed22519() */
/* Constant string value used for the descriptor format. */
@@ -77,6 +78,7 @@
#define str_intro_auth_required "intro-auth-required"
#define str_single_onion "single-onion-service"
#define str_intro_point "introduction-point"
+#define str_ip_onion_key "onion-key"
#define str_ip_auth_key "auth-key"
#define str_ip_enc_key "enc-key"
#define str_ip_enc_key_cert "enc-key-cert"
@@ -135,6 +137,7 @@ static token_rule_t hs_desc_encrypted_v3_token_table[] = {
/* Descriptor ruleset for the introduction points section. */
static token_rule_t hs_desc_intro_point_v3_token_table[] = {
T1_START(str_intro_point, R3_INTRODUCTION_POINT, EQ(1), NO_OBJ),
+ T1N(str_ip_onion_key, R3_INTRO_ONION_KEY, GE(2), OBJ_OK),
T1(str_ip_auth_key, R3_INTRO_AUTH_KEY, NO_ARGS, NEED_OBJ),
T1(str_ip_enc_key, R3_INTRO_ENC_KEY, GE(2), OBJ_OK),
T1(str_ip_enc_key_cert, R3_INTRO_ENC_KEY_CERT, ARGS, OBJ_OK),
@@ -143,31 +146,8 @@ static token_rule_t hs_desc_intro_point_v3_token_table[] = {
END_OF_TABLE
};
-/* Free a descriptor intro point object. */
-STATIC void
-desc_intro_point_free(hs_desc_intro_point_t *ip)
-{
- if (!ip) {
- return;
- }
- if (ip->link_specifiers) {
- SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *,
- ls, tor_free(ls));
- smartlist_free(ip->link_specifiers);
- }
- tor_cert_free(ip->auth_key_cert);
- tor_cert_free(ip->enc_key_cert);
- if (ip->legacy.key) {
- crypto_pk_free(ip->legacy.key);
- }
- if (ip->legacy.cert.encoded) {
- tor_free(ip->legacy.cert.encoded);
- }
- tor_free(ip);
-}
-
/* Free the content of the plaintext section of a descriptor. */
-static void
+STATIC void
desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc)
{
if (!desc) {
@@ -196,7 +176,7 @@ desc_encrypted_data_free_contents(hs_desc_encrypted_data_t *desc)
}
if (desc->intro_points) {
SMARTLIST_FOREACH(desc->intro_points, hs_desc_intro_point_t *, ip,
- desc_intro_point_free(ip));
+ hs_desc_intro_point_free(ip));
smartlist_free(desc->intro_points);
}
memwipe(desc, 0, sizeof(*desc));
@@ -255,7 +235,7 @@ build_secret_input(const hs_descriptor_t *desc, uint8_t *dst, size_t dstlen)
memcpy(dst + offset, desc->subcredential, sizeof(desc->subcredential));
offset += sizeof(desc->subcredential);
/* Copy revision counter value. */
- set_uint64(dst + offset, tor_ntohll(desc->plaintext_data.revision_counter));
+ set_uint64(dst + offset, tor_htonll(desc->plaintext_data.revision_counter));
offset += sizeof(uint64_t);
tor_assert(HS_DESC_ENCRYPTED_SECRET_INPUT_LEN == offset);
}
@@ -351,42 +331,10 @@ encode_link_specifiers(const smartlist_t *specs)
SMARTLIST_FOREACH_BEGIN(specs, const hs_desc_link_specifier_t *,
spec) {
- link_specifier_t *ls = link_specifier_new();
- link_specifier_set_ls_type(ls, spec->type);
-
- switch (spec->type) {
- case LS_IPV4:
- link_specifier_set_un_ipv4_addr(ls,
- tor_addr_to_ipv4h(&spec->u.ap.addr));
- link_specifier_set_un_ipv4_port(ls, spec->u.ap.port);
- /* Four bytes IPv4 and two bytes port. */
- link_specifier_set_ls_len(ls, sizeof(spec->u.ap.addr.addr.in_addr) +
- sizeof(spec->u.ap.port));
- break;
- case LS_IPV6:
- {
- size_t addr_len = link_specifier_getlen_un_ipv6_addr(ls);
- const uint8_t *in6_addr = tor_addr_to_in6_addr8(&spec->u.ap.addr);
- uint8_t *ipv6_array = link_specifier_getarray_un_ipv6_addr(ls);
- memcpy(ipv6_array, in6_addr, addr_len);
- link_specifier_set_un_ipv6_port(ls, spec->u.ap.port);
- /* Sixteen bytes IPv6 and two bytes port. */
- link_specifier_set_ls_len(ls, addr_len + sizeof(spec->u.ap.port));
- break;
+ link_specifier_t *ls = hs_desc_lspec_to_trunnel(spec);
+ if (ls) {
+ link_specifier_list_add_spec(lslist, ls);
}
- case LS_LEGACY_ID:
- {
- size_t legacy_id_len = link_specifier_getlen_un_legacy_id(ls);
- uint8_t *legacy_id_array = link_specifier_getarray_un_legacy_id(ls);
- memcpy(legacy_id_array, spec->u.legacy_id, legacy_id_len);
- link_specifier_set_ls_len(ls, legacy_id_len);
- break;
- }
- default:
- tor_assert(0);
- }
-
- link_specifier_list_add_spec(lslist, ls);
} SMARTLIST_FOREACH_END(spec);
{
@@ -478,6 +426,26 @@ encode_enc_key(const hs_desc_intro_point_t *ip)
return encoded;
}
+/* Encode an introduction point onion key. Return a newly allocated string
+ * with it. On failure, return NULL. */
+static char *
+encode_onion_key(const hs_desc_intro_point_t *ip)
+{
+ char *encoded = NULL;
+ char key_b64[CURVE25519_BASE64_PADDED_LEN + 1];
+
+ tor_assert(ip);
+
+ /* Base64 encode the encryption key for the "onion-key" field. */
+ if (curve25519_public_to_base64(key_b64, &ip->onion_key) < 0) {
+ goto done;
+ }
+ tor_asprintf(&encoded, "%s ntor %s", str_ip_onion_key, key_b64);
+
+ done:
+ return encoded;
+}
+
/* Encode an introduction point object and return a newly allocated string
* with it. On failure, return NULL. */
static char *
@@ -497,6 +465,16 @@ encode_intro_point(const ed25519_public_key_t *sig_key,
tor_free(ls_str);
}
+ /* Onion key encoding. */
+ {
+ char *encoded_onion_key = encode_onion_key(ip);
+ if (encoded_onion_key == NULL) {
+ goto err;
+ }
+ smartlist_add_asprintf(lines, "%s", encoded_onion_key);
+ tor_free(encoded_onion_key);
+ }
+
/* Authentication key encoding. */
{
char *encoded_cert;
@@ -987,6 +965,10 @@ 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;
@@ -1133,6 +1115,15 @@ decode_link_specifiers(const char *encoded)
memcpy(hs_spec->u.legacy_id, link_specifier_getarray_un_legacy_id(ls),
sizeof(hs_spec->u.legacy_id));
break;
+ case LS_ED25519_ID:
+ /* Both are known at compile time so let's make sure they are the same
+ * else we can copy memory out of bound. */
+ tor_assert(link_specifier_getlen_un_ed25519_id(ls) ==
+ sizeof(hs_spec->u.ed25519_id));
+ memcpy(hs_spec->u.ed25519_id,
+ link_specifier_getconstarray_un_ed25519_id(ls),
+ sizeof(hs_spec->u.ed25519_id));
+ break;
default:
goto err;
}
@@ -1241,7 +1232,7 @@ cert_is_valid(tor_cert_t *cert, uint8_t type, const char *log_obj_type)
}
/* The following will not only check if the signature matches but also the
* expiration date and overall validity. */
- if (tor_cert_checksig(cert, &cert->signing_key, time(NULL)) < 0) {
+ if (tor_cert_checksig(cert, &cert->signing_key, approx_time()) < 0) {
log_warn(LD_REND, "Invalid signature for %s.", log_obj_type);
goto err;
}
@@ -1625,6 +1616,50 @@ decode_intro_legacy_key(const directory_token_t *tok,
return -1;
}
+/* Dig into the descriptor <b>tokens</b> to find the onion key we should use
+ * for this intro point, and set it into <b>onion_key_out</b>. Return 0 if it
+ * was found and well-formed, otherwise return -1 in case of errors. */
+static int
+set_intro_point_onion_key(curve25519_public_key_t *onion_key_out,
+ const smartlist_t *tokens)
+{
+ int retval = -1;
+ smartlist_t *onion_keys = NULL;
+
+ tor_assert(onion_key_out);
+
+ onion_keys = find_all_by_keyword(tokens, R3_INTRO_ONION_KEY);
+ if (!onion_keys) {
+ log_warn(LD_REND, "Descriptor did not contain intro onion keys");
+ goto err;
+ }
+
+ SMARTLIST_FOREACH_BEGIN(onion_keys, directory_token_t *, tok) {
+ /* This field is using GE(2) so for possible forward compatibility, we
+ * accept more fields but must be at least 2. */
+ tor_assert(tok->n_args >= 2);
+
+ /* Try to find an ntor key, it's the only recognized type right now */
+ if (!strcmp(tok->args[0], "ntor")) {
+ if (curve25519_public_from_base64(onion_key_out, tok->args[1]) < 0) {
+ log_warn(LD_REND, "Introduction point ntor onion-key is invalid");
+ goto err;
+ }
+ /* Got the onion key! Set the appropriate retval */
+ retval = 0;
+ }
+ } SMARTLIST_FOREACH_END(tok);
+
+ /* Log an error if we didn't find it :( */
+ if (retval < 0) {
+ log_warn(LD_REND, "Descriptor did not contain ntor onion keys");
+ }
+
+ err:
+ smartlist_free(onion_keys);
+ return retval;
+}
+
/* Given the start of a section and the end of it, decode a single
* introduction point from that section. Return a newly allocated introduction
* point object containing the decoded data. Return NULL if the section can't
@@ -1650,17 +1685,24 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start)
/* Ok we seem to have a well formed section containing enough tokens to
* parse. Allocate our IP object and try to populate it. */
- ip = tor_malloc_zero(sizeof(hs_desc_intro_point_t));
+ ip = hs_desc_intro_point_new();
/* "introduction-point" SP link-specifiers NL */
tok = find_by_keyword(tokens, R3_INTRODUCTION_POINT);
tor_assert(tok->n_args == 1);
+ /* Our constructor creates this list by default so free it. */
+ smartlist_free(ip->link_specifiers);
ip->link_specifiers = decode_link_specifiers(tok->args[0]);
if (!ip->link_specifiers) {
log_warn(LD_REND, "Introduction point has invalid link specifiers");
goto err;
}
+ /* "onion-key" SP ntor SP key NL */
+ if (set_intro_point_onion_key(&ip->onion_key, tokens) < 0) {
+ goto err;
+ }
+
/* "auth-key" NL certificate NL */
tok = find_by_keyword(tokens, R3_INTRO_AUTH_KEY);
tor_assert(tok->object_body);
@@ -1732,7 +1774,7 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start)
goto done;
err:
- desc_intro_point_free(ip);
+ hs_desc_intro_point_free(ip);
ip = NULL;
done:
@@ -1747,18 +1789,13 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start)
/* Given a descriptor string at <b>data</b>, decode all possible introduction
* points that we can find. Add the introduction point object to desc_enc as we
- * find them. Return 0 on success.
- *
- * On error, a negative value is returned. It is possible that some intro
- * point object have been added to the desc_enc, they should be considered
- * invalid. One single bad encoded introduction point will make this function
- * return an error. */
-STATIC int
+ * find them. This function can't fail and it is possible that zero
+ * introduction points can be decoded. */
+static void
decode_intro_points(const hs_descriptor_t *desc,
hs_desc_encrypted_data_t *desc_enc,
const char *data)
{
- int retval = -1;
smartlist_t *chunked_desc = smartlist_new();
smartlist_t *intro_points = smartlist_new();
@@ -1799,22 +1836,19 @@ decode_intro_points(const hs_descriptor_t *desc,
SMARTLIST_FOREACH_BEGIN(intro_points, const char *, intro_point) {
hs_desc_intro_point_t *ip = decode_introduction_point(desc, intro_point);
if (!ip) {
- /* Malformed introduction point section. Stop right away, this
- * descriptor shouldn't be used. */
- goto err;
+ /* Malformed introduction point section. We'll ignore this introduction
+ * point and continue parsing. New or unknown fields are possible for
+ * forward compatibility. */
+ continue;
}
smartlist_add(desc_enc->intro_points, ip);
} SMARTLIST_FOREACH_END(intro_point);
done:
- retval = 0;
-
- err:
SMARTLIST_FOREACH(chunked_desc, char *, a, tor_free(a));
smartlist_free(chunked_desc);
SMARTLIST_FOREACH(intro_points, char *, a, tor_free(a));
smartlist_free(intro_points);
- return retval;
}
/* Return 1 iff the given base64 encoded signature in b64_sig from the encoded
* descriptor in encoded_desc validates the descriptor content. */
@@ -1852,7 +1886,8 @@ desc_sig_is_valid(const char *b64_sig,
sig_start = tor_memstr(encoded_desc, encoded_len, "\n" str_signature);
/* Getting here means the token parsing worked for the signature so if we
* can't find the start of the signature, we have a code flow issue. */
- if (BUG(!sig_start)) {
+ if (!sig_start) {
+ log_warn(LD_GENERAL, "Malformed signature line. Rejecting.");
goto err;
}
/* Skip newline, it has to go in the signature check. */
@@ -2040,14 +2075,14 @@ desc_decode_encrypted_v3(const hs_descriptor_t *desc,
/* Initialize the descriptor's introduction point list before we start
* decoding. Having 0 intro point is valid. Then decode them all. */
desc_encrypted_out->intro_points = smartlist_new();
- if (decode_intro_points(desc, desc_encrypted_out, message) < 0) {
- goto err;
- }
+ decode_intro_points(desc, desc_encrypted_out, message);
+
/* Validation of maximum introduction points allowed. */
- if (smartlist_len(desc_encrypted_out->intro_points) > MAX_INTRO_POINTS) {
+ if (smartlist_len(desc_encrypted_out->intro_points) >
+ HS_CONFIG_V3_MAX_INTRO_POINTS) {
log_warn(LD_REND, "Service descriptor contains too many introduction "
"points. Maximum allowed is %d but we have %d",
- MAX_INTRO_POINTS,
+ HS_CONFIG_V3_MAX_INTRO_POINTS,
smartlist_len(desc_encrypted_out->intro_points));
goto err;
}
@@ -2222,7 +2257,7 @@ hs_desc_decode_descriptor(const char *encoded,
const uint8_t *subcredential,
hs_descriptor_t **desc_out)
{
- int ret;
+ int ret = -1;
hs_descriptor_t *desc;
tor_assert(encoded);
@@ -2230,10 +2265,13 @@ hs_desc_decode_descriptor(const char *encoded,
desc = tor_malloc_zero(sizeof(hs_descriptor_t));
/* Subcredentials are optional. */
- if (subcredential) {
- memcpy(desc->subcredential, subcredential, sizeof(desc->subcredential));
+ if (BUG(!subcredential)) {
+ log_warn(LD_GENERAL, "Tried to decrypt without subcred. Impossible!");
+ goto err;
}
+ memcpy(desc->subcredential, subcredential, sizeof(desc->subcredential));
+
ret = hs_desc_decode_plaintext(encoded, &desc->plaintext_data);
if (ret < 0) {
goto err;
@@ -2279,10 +2317,10 @@ static int
*
* Return 0 on success and encoded_out is a valid pointer. On error, -1 is
* returned and encoded_out is set to NULL. */
-int
-hs_desc_encode_descriptor(const hs_descriptor_t *desc,
- const ed25519_keypair_t *signing_kp,
- char **encoded_out)
+MOCK_IMPL(int,
+hs_desc_encode_descriptor,(const hs_descriptor_t *desc,
+ const ed25519_keypair_t *signing_kp,
+ char **encoded_out))
{
int ret = -1;
uint32_t version;
@@ -2359,3 +2397,197 @@ hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data)
data->superencrypted_blob_size);
}
+/* Return the size in bytes of the given encrypted data object. Used by OOM
+ * subsystem. */
+static size_t
+hs_desc_encrypted_obj_size(const hs_desc_encrypted_data_t *data)
+{
+ tor_assert(data);
+ size_t intro_size = 0;
+ if (data->intro_auth_types) {
+ intro_size +=
+ smartlist_len(data->intro_auth_types) * sizeof(intro_auth_types);
+ }
+ if (data->intro_points) {
+ /* XXX could follow pointers here and get more accurate size */
+ intro_size +=
+ smartlist_len(data->intro_points) * sizeof(hs_desc_intro_point_t);
+ }
+
+ return sizeof(*data) + intro_size;
+}
+
+/* Return the size in bytes of the given descriptor object. Used by OOM
+ * subsystem. */
+ size_t
+hs_desc_obj_size(const hs_descriptor_t *data)
+{
+ tor_assert(data);
+ return (hs_desc_plaintext_obj_size(&data->plaintext_data) +
+ hs_desc_encrypted_obj_size(&data->encrypted_data) +
+ sizeof(data->subcredential));
+}
+
+/* Return a newly allocated descriptor intro point. */
+hs_desc_intro_point_t *
+hs_desc_intro_point_new(void)
+{
+ hs_desc_intro_point_t *ip = tor_malloc_zero(sizeof(*ip));
+ ip->link_specifiers = smartlist_new();
+ return ip;
+}
+
+/* Free a descriptor intro point object. */
+void
+hs_desc_intro_point_free(hs_desc_intro_point_t *ip)
+{
+ if (ip == NULL) {
+ return;
+ }
+ if (ip->link_specifiers) {
+ SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *,
+ ls, hs_desc_link_specifier_free(ls));
+ smartlist_free(ip->link_specifiers);
+ }
+ tor_cert_free(ip->auth_key_cert);
+ tor_cert_free(ip->enc_key_cert);
+ crypto_pk_free(ip->legacy.key);
+ tor_free(ip->legacy.cert.encoded);
+ tor_free(ip);
+}
+
+/* Free the given descriptor link specifier. */
+void
+hs_desc_link_specifier_free(hs_desc_link_specifier_t *ls)
+{
+ if (ls == NULL) {
+ return;
+ }
+ tor_free(ls);
+}
+
+/* Return a newly allocated descriptor link specifier using the given extend
+ * info and requested type. Return NULL on error. */
+hs_desc_link_specifier_t *
+hs_desc_link_specifier_new(const extend_info_t *info, uint8_t type)
+{
+ hs_desc_link_specifier_t *ls = NULL;
+
+ tor_assert(info);
+
+ ls = tor_malloc_zero(sizeof(*ls));
+ ls->type = type;
+ switch (ls->type) {
+ case LS_IPV4:
+ if (info->addr.family != AF_INET) {
+ goto err;
+ }
+ tor_addr_copy(&ls->u.ap.addr, &info->addr);
+ ls->u.ap.port = info->port;
+ break;
+ case LS_IPV6:
+ if (info->addr.family != AF_INET6) {
+ goto err;
+ }
+ tor_addr_copy(&ls->u.ap.addr, &info->addr);
+ ls->u.ap.port = info->port;
+ break;
+ case LS_LEGACY_ID:
+ /* Bug out if the identity digest is not set */
+ if (BUG(tor_mem_is_zero(info->identity_digest,
+ sizeof(info->identity_digest)))) {
+ goto err;
+ }
+ memcpy(ls->u.legacy_id, info->identity_digest, sizeof(ls->u.legacy_id));
+ break;
+ case LS_ED25519_ID:
+ /* ed25519 keys are optional for intro points */
+ if (ed25519_public_key_is_zero(&info->ed_identity)) {
+ goto err;
+ }
+ memcpy(ls->u.ed25519_id, info->ed_identity.pubkey,
+ sizeof(ls->u.ed25519_id));
+ break;
+ default:
+ /* Unknown type is code flow error. */
+ tor_assert(0);
+ }
+
+ return ls;
+ err:
+ tor_free(ls);
+ return NULL;
+}
+
+/* From the given descriptor, remove and free every introduction point. */
+void
+hs_descriptor_clear_intro_points(hs_descriptor_t *desc)
+{
+ smartlist_t *ips;
+
+ tor_assert(desc);
+
+ ips = desc->encrypted_data.intro_points;
+ if (ips) {
+ SMARTLIST_FOREACH(ips, hs_desc_intro_point_t *,
+ ip, hs_desc_intro_point_free(ip));
+ smartlist_clear(ips);
+ }
+}
+
+/* From a descriptor link specifier object spec, returned a newly allocated
+ * link specifier object that is the encoded representation of spec. Return
+ * NULL on error. */
+link_specifier_t *
+hs_desc_lspec_to_trunnel(const hs_desc_link_specifier_t *spec)
+{
+ tor_assert(spec);
+
+ link_specifier_t *ls = link_specifier_new();
+ link_specifier_set_ls_type(ls, spec->type);
+
+ switch (spec->type) {
+ case LS_IPV4:
+ link_specifier_set_un_ipv4_addr(ls,
+ tor_addr_to_ipv4h(&spec->u.ap.addr));
+ link_specifier_set_un_ipv4_port(ls, spec->u.ap.port);
+ /* Four bytes IPv4 and two bytes port. */
+ link_specifier_set_ls_len(ls, sizeof(spec->u.ap.addr.addr.in_addr) +
+ sizeof(spec->u.ap.port));
+ break;
+ case LS_IPV6:
+ {
+ size_t addr_len = link_specifier_getlen_un_ipv6_addr(ls);
+ const uint8_t *in6_addr = tor_addr_to_in6_addr8(&spec->u.ap.addr);
+ uint8_t *ipv6_array = link_specifier_getarray_un_ipv6_addr(ls);
+ memcpy(ipv6_array, in6_addr, addr_len);
+ link_specifier_set_un_ipv6_port(ls, spec->u.ap.port);
+ /* Sixteen bytes IPv6 and two bytes port. */
+ link_specifier_set_ls_len(ls, addr_len + sizeof(spec->u.ap.port));
+ break;
+ }
+ case LS_LEGACY_ID:
+ {
+ size_t legacy_id_len = link_specifier_getlen_un_legacy_id(ls);
+ uint8_t *legacy_id_array = link_specifier_getarray_un_legacy_id(ls);
+ memcpy(legacy_id_array, spec->u.legacy_id, legacy_id_len);
+ link_specifier_set_ls_len(ls, legacy_id_len);
+ break;
+ }
+ case LS_ED25519_ID:
+ {
+ size_t ed25519_id_len = link_specifier_getlen_un_ed25519_id(ls);
+ uint8_t *ed25519_id_array = link_specifier_getarray_un_ed25519_id(ls);
+ memcpy(ed25519_id_array, spec->u.ed25519_id, ed25519_id_len);
+ link_specifier_set_ls_len(ls, ed25519_id_len);
+ break;
+ }
+ default:
+ tor_assert_nonfatal_unreached();
+ link_specifier_free(ls);
+ ls = NULL;
+ }
+
+ return ls;
+}
+
diff --git a/src/or/hs_descriptor.h b/src/or/hs_descriptor.h
index b8b94792de..3e82746c35 100644
--- a/src/or/hs_descriptor.h
+++ b/src/or/hs_descriptor.h
@@ -18,17 +18,23 @@
#include "crypto_ed25519.h"
#include "torcert.h"
+/* Trunnel */
+struct link_specifier_t;
+
/* The earliest descriptor format version we support. */
#define HS_DESC_SUPPORTED_FORMAT_VERSION_MIN 3
/* The latest descriptor format version we support. */
#define HS_DESC_SUPPORTED_FORMAT_VERSION_MAX 3
+/* Default lifetime of a descriptor in seconds. The valus is set at 3 hours
+ * which is 180 minutes or 10800 seconds. */
+#define HS_DESC_DEFAULT_LIFETIME (3 * 60 * 60)
/* Maximum lifetime of a descriptor in seconds. The value is set at 12 hours
* which is 720 minutes or 43200 seconds. */
#define HS_DESC_MAX_LIFETIME (12 * 60 * 60)
/* Lifetime of certificate in the descriptor. This defines the lifetime of the
* descriptor signing key and the cross certification cert of that key. */
-#define HS_DESC_CERT_LIFETIME (24 * 60 * 60)
+#define HS_DESC_CERT_LIFETIME (36 * 60 * 60)
/* Length of the salt needed for the encrypted section of a descriptor. */
#define HS_DESC_ENCRYPTED_SALT_LEN 16
/* Length of the secret input needed for the KDF construction which derives
@@ -65,12 +71,14 @@ typedef struct hs_desc_link_specifier_t {
* specification. */
uint8_t type;
- /* It's either an address/port or a legacy identity fingerprint. */
+ /* It must be one of these types, can't be more than one. */
union {
/* IP address and port of the relay use to extend. */
tor_addr_port_t ap;
/* Legacy identity. A 20-byte SHA1 identity fingerprint. */
uint8_t legacy_id[DIGEST_LEN];
+ /* ed25519 identity. A 32-byte key. */
+ uint8_t ed25519_id[ED25519_PUBKEY_LEN];
} u;
} hs_desc_link_specifier_t;
@@ -80,6 +88,10 @@ typedef struct hs_desc_intro_point_t {
* contains hs_desc_link_specifier_t object. It MUST have at least one. */
smartlist_t *link_specifiers;
+ /* Onion key of the introduction point used to extend to it for the ntor
+ * handshake. */
+ curve25519_public_key_t onion_key;
+
/* Authentication key used to establish the introduction point circuit and
* cross-certifies the blinded public key for the replica thus signed by
* the blinded key and in turn signs it. */
@@ -197,9 +209,15 @@ void hs_descriptor_free(hs_descriptor_t *desc);
void hs_desc_plaintext_data_free(hs_desc_plaintext_data_t *desc);
void hs_desc_encrypted_data_free(hs_desc_encrypted_data_t *desc);
-int hs_desc_encode_descriptor(const hs_descriptor_t *desc,
- const ed25519_keypair_t *signing_kp,
- char **encoded_out);
+void hs_desc_link_specifier_free(hs_desc_link_specifier_t *ls);
+hs_desc_link_specifier_t *hs_desc_link_specifier_new(
+ const extend_info_t *info, uint8_t type);
+void hs_descriptor_clear_intro_points(hs_descriptor_t *desc);
+
+MOCK_DECL(int,
+ hs_desc_encode_descriptor,(const hs_descriptor_t *desc,
+ const ed25519_keypair_t *signing_kp,
+ char **encoded_out));
int hs_desc_decode_descriptor(const char *encoded,
const uint8_t *subcredential,
@@ -209,8 +227,15 @@ int hs_desc_decode_plaintext(const char *encoded,
int hs_desc_decode_encrypted(const hs_descriptor_t *desc,
hs_desc_encrypted_data_t *desc_out);
+size_t hs_desc_obj_size(const hs_descriptor_t *data);
size_t hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data);
+hs_desc_intro_point_t *hs_desc_intro_point_new(void);
+void hs_desc_intro_point_free(hs_desc_intro_point_t *ip);
+
+link_specifier_t *hs_desc_lspec_to_trunnel(
+ const hs_desc_link_specifier_t *spec);
+
#ifdef HS_DESCRIPTOR_PRIVATE
/* Encoding. */
@@ -223,18 +248,16 @@ STATIC smartlist_t *decode_link_specifiers(const char *encoded);
STATIC hs_desc_intro_point_t *decode_introduction_point(
const hs_descriptor_t *desc,
const char *text);
-STATIC int decode_intro_points(const hs_descriptor_t *desc,
- hs_desc_encrypted_data_t *desc_enc,
- const char *data);
STATIC int encrypted_data_length_is_valid(size_t len);
STATIC int cert_is_valid(tor_cert_t *cert, uint8_t type,
const char *log_obj_type);
STATIC int desc_sig_is_valid(const char *b64_sig,
const ed25519_public_key_t *signing_pubkey,
const char *encoded_desc, size_t encoded_len);
-STATIC void desc_intro_point_free(hs_desc_intro_point_t *ip);
STATIC size_t decode_superencrypted(const char *message, size_t message_len,
uint8_t **encrypted_out);
+STATIC void desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc);
+
#endif /* HS_DESCRIPTOR_PRIVATE */
#endif /* TOR_HS_DESCRIPTOR_H */
diff --git a/src/or/hs_ident.c b/src/or/hs_ident.c
new file mode 100644
index 0000000000..df39285158
--- /dev/null
+++ b/src/or/hs_ident.c
@@ -0,0 +1,110 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_ident.c
+ * \brief Contains circuit and connection identifier code for the whole HS
+ * subsytem.
+ **/
+
+#include "hs_ident.h"
+
+/* Return a newly allocated circuit identifier. The given public key is copied
+ * identity_pk into the identifier. */
+hs_ident_circuit_t *
+hs_ident_circuit_new(const ed25519_public_key_t *identity_pk,
+ hs_ident_circuit_type_t circuit_type)
+{
+ tor_assert(circuit_type == HS_IDENT_CIRCUIT_INTRO ||
+ circuit_type == HS_IDENT_CIRCUIT_RENDEZVOUS);
+ hs_ident_circuit_t *ident = tor_malloc_zero(sizeof(*ident));
+ ed25519_pubkey_copy(&ident->identity_pk, identity_pk);
+ ident->circuit_type = circuit_type;
+ return ident;
+}
+
+/* Free the given circuit identifier. */
+void
+hs_ident_circuit_free(hs_ident_circuit_t *ident)
+{
+ if (ident == NULL) {
+ return;
+ }
+ memwipe(ident, 0, sizeof(hs_ident_circuit_t));
+ tor_free(ident);
+}
+
+/* For a given circuit identifier src, return a newly allocated copy of it.
+ * This can't fail. */
+hs_ident_circuit_t *
+hs_ident_circuit_dup(const hs_ident_circuit_t *src)
+{
+ hs_ident_circuit_t *ident = tor_malloc_zero(sizeof(*ident));
+ memcpy(ident, src, sizeof(*ident));
+ return ident;
+}
+
+/* For a given directory connection identifier src, return a newly allocated
+ * copy of it. This can't fail. */
+hs_ident_dir_conn_t *
+hs_ident_dir_conn_dup(const hs_ident_dir_conn_t *src)
+{
+ hs_ident_dir_conn_t *ident = tor_malloc_zero(sizeof(*ident));
+ memcpy(ident, src, sizeof(*ident));
+ return ident;
+}
+
+/* Free the given directory connection identifier. */
+void
+hs_ident_dir_conn_free(hs_ident_dir_conn_t *ident)
+{
+ if (ident == NULL) {
+ return;
+ }
+ memwipe(ident, 0, sizeof(hs_ident_dir_conn_t));
+ tor_free(ident);
+}
+
+/* Return a newly allocated edge connection identifier. The given public key
+ * identity_pk is copied into the identifier. */
+hs_ident_edge_conn_t *
+hs_ident_edge_conn_new(const ed25519_public_key_t *identity_pk)
+{
+ hs_ident_edge_conn_t *ident = tor_malloc_zero(sizeof(*ident));
+ ed25519_pubkey_copy(&ident->identity_pk, identity_pk);
+ return ident;
+}
+
+/* Free the given edge connection identifier. */
+void
+hs_ident_edge_conn_free(hs_ident_edge_conn_t *ident)
+{
+ if (ident == NULL) {
+ return;
+ }
+ memwipe(ident, 0, sizeof(hs_ident_edge_conn_t));
+ tor_free(ident);
+}
+
+/* Return true if the given ident is valid for an introduction circuit. */
+int
+hs_ident_intro_circ_is_valid(const hs_ident_circuit_t *ident)
+{
+ if (ident == NULL) {
+ goto invalid;
+ }
+
+ if (ed25519_public_key_is_zero(&ident->identity_pk)) {
+ goto invalid;
+ }
+
+ if (ed25519_public_key_is_zero(&ident->intro_auth_pk)) {
+ goto invalid;
+ }
+
+ /* Valid. */
+ return 1;
+ invalid:
+ return 0;
+}
+
diff --git a/src/or/hs_ident.h b/src/or/hs_ident.h
new file mode 100644
index 0000000000..cfcde781d1
--- /dev/null
+++ b/src/or/hs_ident.h
@@ -0,0 +1,133 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_ident.h
+ * \brief Header file containing circuit and connection identifier data for
+ * the whole HS subsytem.
+ *
+ * \details
+ * This interface is used to uniquely identify a hidden service on a circuit
+ * or connection using the service identity public key. Once the circuit or
+ * connection subsystem calls in the hidden service one, we use those
+ * identifiers to lookup the corresponding objects like service, intro point
+ * and descriptor.
+ *
+ * Furthermore, the circuit identifier holds cryptographic material needed for
+ * the e2e encryption on the rendezvous circuit which is set once the
+ * rendezvous circuit has opened and ready to be used.
+ **/
+
+#ifndef TOR_HS_IDENT_H
+#define TOR_HS_IDENT_H
+
+#include "crypto.h"
+#include "crypto_ed25519.h"
+
+#include "hs_common.h"
+
+/* Length of the rendezvous cookie that is used to connect circuits at the
+ * rendezvous point. */
+#define HS_REND_COOKIE_LEN DIGEST_LEN
+
+/* Type of circuit an hs_ident_t object is associated with. */
+typedef enum {
+ HS_IDENT_CIRCUIT_INTRO = 1,
+ HS_IDENT_CIRCUIT_RENDEZVOUS = 2,
+} hs_ident_circuit_type_t;
+
+/* Client and service side circuit identifier that is used for hidden service
+ * circuit establishment. Not all fields contain data, it depends on the
+ * circuit purpose. This is attached to an origin_circuit_t. All fields are
+ * used by both client and service. */
+typedef struct hs_ident_circuit_t {
+ /* (All circuit) The public key used to uniquely identify the service. It is
+ * the one found in the onion address. */
+ ed25519_public_key_t identity_pk;
+
+ /* (All circuit) The type of circuit this identifier is attached to.
+ * Accessors of the fields in this object assert non fatal on this circuit
+ * type. In other words, if a rendezvous field is being accessed, the
+ * circuit type MUST BE of type HS_IDENT_CIRCUIT_RENDEZVOUS. This value is
+ * set when an object is initialized in its constructor. */
+ hs_ident_circuit_type_t circuit_type;
+
+ /* (All circuit) Introduction point authentication key. It's also needed on
+ * the rendezvous circuit for the ntor handshake. It's used as the unique key
+ * of the introduction point so it should not be shared between multiple
+ * intro points. */
+ ed25519_public_key_t intro_auth_pk;
+
+ /* (Only client rendezvous circuit) Introduction point encryption public
+ * key. We keep it in the rendezvous identifier for the ntor handshake. */
+ curve25519_public_key_t intro_enc_pk;
+
+ /* (Only rendezvous circuit) Rendezvous cookie sent from the client to the
+ * service with an INTRODUCE1 cell and used by the service in an
+ * RENDEZVOUS1 cell. */
+ uint8_t rendezvous_cookie[HS_REND_COOKIE_LEN];
+
+ /* (Only service rendezvous circuit) The HANDSHAKE_INFO needed in the
+ * RENDEZVOUS1 cell of the service. The construction is as follows:
+ * SERVER_PK [32 bytes]
+ * AUTH_MAC [32 bytes]
+ */
+ uint8_t rendezvous_handshake_info[CURVE25519_PUBKEY_LEN + DIGEST256_LEN];
+
+ /* (Only client rendezvous circuit) Client ephemeral keypair needed for the
+ * e2e encryption with the service. */
+ curve25519_keypair_t rendezvous_client_kp;
+
+ /* (Only rendezvous circuit) The NTOR_KEY_SEED needed for key derivation for
+ * the e2e encryption with the client on the circuit. */
+ uint8_t rendezvous_ntor_key_seed[DIGEST256_LEN];
+
+ /* (Only rendezvous circuit) Number of streams associated with this
+ * rendezvous circuit. We track this because there is a check on a maximum
+ * value. */
+ uint64_t num_rdv_streams;
+} hs_ident_circuit_t;
+
+/* Client and service side directory connection identifier used for a
+ * directory connection to identify which service is being queried. This is
+ * attached to a dir_connection_t. */
+typedef struct hs_ident_dir_conn_t {
+ /* The public key used to uniquely identify the service. It is the one found
+ * in the onion address. */
+ ed25519_public_key_t identity_pk;
+
+ /* XXX: Client authorization. */
+} hs_ident_dir_conn_t;
+
+/* Client and service side edge connection identifier used for an edge
+ * connection to identify which service is being queried. This is attached to
+ * a edge_connection_t. */
+typedef struct hs_ident_edge_conn_t {
+ /* The public key used to uniquely identify the service. It is the one found
+ * in the onion address. */
+ ed25519_public_key_t identity_pk;
+
+ /* XXX: Client authorization. */
+} hs_ident_edge_conn_t;
+
+/* Circuit identifier API. */
+hs_ident_circuit_t *hs_ident_circuit_new(
+ const ed25519_public_key_t *identity_pk,
+ hs_ident_circuit_type_t circuit_type);
+void hs_ident_circuit_free(hs_ident_circuit_t *ident);
+hs_ident_circuit_t *hs_ident_circuit_dup(const hs_ident_circuit_t *src);
+
+/* Directory connection identifier API. */
+hs_ident_dir_conn_t *hs_ident_dir_conn_dup(const hs_ident_dir_conn_t *src);
+void hs_ident_dir_conn_free(hs_ident_dir_conn_t *ident);
+
+/* Edge connection identifier API. */
+hs_ident_edge_conn_t *hs_ident_edge_conn_new(
+ const ed25519_public_key_t *identity_pk);
+void hs_ident_edge_conn_free(hs_ident_edge_conn_t *ident);
+
+/* Validators */
+int hs_ident_intro_circ_is_valid(const hs_ident_circuit_t *ident);
+
+#endif /* TOR_HS_IDENT_H */
+
diff --git a/src/or/hs_intropoint.c b/src/or/hs_intropoint.c
index 2abbfcd6c3..cb4d6c02e5 100644
--- a/src/or/hs_intropoint.c
+++ b/src/or/hs_intropoint.c
@@ -24,6 +24,7 @@
#include "hs/cell_introduce1.h"
#include "hs_circuitmap.h"
+#include "hs_descriptor.h"
#include "hs_intropoint.h"
#include "hs_common.h"
@@ -191,7 +192,7 @@ handle_verified_establish_intro_cell(or_circuit_t *circ,
/* Then notify the hidden service that the intro point is established by
sending an INTRO_ESTABLISHED cell */
if (hs_intro_send_intro_established_cell(circ)) {
- log_warn(LD_BUG, "Couldn't send INTRO_ESTABLISHED cell.");
+ log_warn(LD_PROTOCOL, "Couldn't send INTRO_ESTABLISHED cell.");
return -1;
}
@@ -249,9 +250,6 @@ handle_establish_intro(or_circuit_t *circ, const uint8_t *request,
goto err;
}
- log_warn(LD_GENERAL, "Established prop224 intro point on circuit %" PRIu32,
- circ->p_circ_id);
-
/* We are done! */
retval = 0;
goto done;
@@ -481,7 +479,7 @@ handle_introduce1(or_circuit_t *client_circ, const uint8_t *request,
if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(service_circ),
RELAY_COMMAND_INTRODUCE2,
(char *) request, request_len, NULL)) {
- log_warn(LD_REND, "Unable to send INTRODUCE2 cell to the service.");
+ log_warn(LD_PROTOCOL, "Unable to send INTRODUCE2 cell to the service.");
/* Inform the client that we can't relay the cell. */
status = HS_INTRO_ACK_STATUS_CANT_RELAY;
goto send_ack;
@@ -494,8 +492,8 @@ handle_introduce1(or_circuit_t *client_circ, const uint8_t *request,
send_ack:
/* Send INTRODUCE_ACK or INTRODUCE_NACK to client */
if (send_introduce_ack_cell(client_circ, status) < 0) {
- log_warn(LD_REND, "Unable to send an INTRODUCE ACK status %d to client.",
- status);
+ log_warn(LD_PROTOCOL, "Unable to send an INTRODUCE ACK status %d "
+ "to client.", status);
/* Circuit has been closed on failure of transmission. */
goto done;
}
@@ -594,3 +592,18 @@ hs_intro_received_introduce1(or_circuit_t *circ, const uint8_t *request,
return -1;
}
+/* Clear memory allocated by the given intropoint object ip (but don't free the
+ * object itself). */
+void
+hs_intropoint_clear(hs_intropoint_t *ip)
+{
+ if (ip == NULL) {
+ return;
+ }
+ tor_cert_free(ip->auth_key_cert);
+ SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *, ls,
+ hs_desc_link_specifier_free(ls));
+ smartlist_free(ip->link_specifiers);
+ memset(ip, 0, sizeof(hs_intropoint_t));
+}
+
diff --git a/src/or/hs_intropoint.h b/src/or/hs_intropoint.h
index 163ed810e7..5c77f07ec3 100644
--- a/src/or/hs_intropoint.h
+++ b/src/or/hs_intropoint.h
@@ -9,12 +9,15 @@
#ifndef TOR_HS_INTRO_H
#define TOR_HS_INTRO_H
+#include "crypto_curve25519.h"
+#include "torcert.h"
+
/* Authentication key type in an ESTABLISH_INTRO cell. */
-enum hs_intro_auth_key_type {
+typedef enum {
HS_INTRO_AUTH_KEY_TYPE_LEGACY0 = 0x00,
HS_INTRO_AUTH_KEY_TYPE_LEGACY1 = 0x01,
HS_INTRO_AUTH_KEY_TYPE_ED25519 = 0x02,
-};
+} hs_intro_auth_key_type_t;
/* INTRODUCE_ACK status code. */
typedef enum {
@@ -24,6 +27,18 @@ typedef enum {
HS_INTRO_ACK_STATUS_CANT_RELAY = 0x0003,
} hs_intro_ack_status_t;
+/* Object containing introduction point common data between the service and
+ * the client side. */
+typedef struct hs_intropoint_t {
+ /* Does this intro point only supports legacy ID ?. */
+ unsigned int is_only_legacy : 1;
+
+ /* Authentication key certificate from the descriptor. */
+ tor_cert_t *auth_key_cert;
+ /* A list of link specifier. */
+ smartlist_t *link_specifiers;
+} hs_intropoint_t;
+
int hs_intro_received_establish_intro(or_circuit_t *circ,
const uint8_t *request,
size_t request_len);
@@ -35,6 +50,9 @@ MOCK_DECL(int, hs_intro_send_intro_established_cell,(or_circuit_t *circ));
/* also used by rendservice.c */
int hs_intro_circuit_is_suitable_for_establish_intro(const or_circuit_t *circ);
+hs_intropoint_t *hs_intro_new(void);
+void hs_intropoint_clear(hs_intropoint_t *ip);
+
#ifdef HS_INTROPOINT_PRIVATE
#include "hs/cell_establish_intro.h"
diff --git a/src/or/hs_ntor.c b/src/or/hs_ntor.c
index 119899817e..a416bc46c3 100644
--- a/src/or/hs_ntor.c
+++ b/src/or/hs_ntor.c
@@ -578,49 +578,41 @@ hs_ntor_client_rendezvous2_mac_is_good(
/* Input length to KDF for key expansion */
#define NTOR_KEY_EXPANSION_KDF_INPUT_LEN (DIGEST256_LEN + M_HSEXPAND_LEN)
-/* Output length of KDF for key expansion */
-#define NTOR_KEY_EXPANSION_KDF_OUTPUT_LEN (DIGEST256_LEN*3+CIPHER256_KEY_LEN*2)
-
-/** Given the rendezvous key material in <b>hs_ntor_rend_cell_keys</b>, do the
- * circuit key expansion as specified by section '4.2.1. Key expansion' and
- * return a hs_ntor_rend_circuit_keys_t structure with the computed keys. */
-hs_ntor_rend_circuit_keys_t *
-hs_ntor_circuit_key_expansion(
- const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys)
+
+/** Given the rendezvous key seed in <b>ntor_key_seed</b> (of size
+ * DIGEST256_LEN), do the circuit key expansion as specified by section
+ * '4.2.1. Key expansion' and place the keys in <b>keys_out</b> (which must be
+ * of size HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN).
+ *
+ * Return 0 if things went well, else return -1. */
+int
+hs_ntor_circuit_key_expansion(const uint8_t *ntor_key_seed, size_t seed_len,
+ uint8_t *keys_out, size_t keys_out_len)
{
uint8_t *ptr;
uint8_t kdf_input[NTOR_KEY_EXPANSION_KDF_INPUT_LEN];
- uint8_t keys[NTOR_KEY_EXPANSION_KDF_OUTPUT_LEN];
crypto_xof_t *xof;
- hs_ntor_rend_circuit_keys_t *rend_circuit_keys = NULL;
+
+ /* Sanity checks on lengths to make sure we are good */
+ if (BUG(seed_len != DIGEST256_LEN)) {
+ return -1;
+ }
+ if (BUG(keys_out_len != HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN)) {
+ return -1;
+ }
/* Let's build the input to the KDF */
ptr = kdf_input;
- APPEND(ptr, hs_ntor_rend_cell_keys->ntor_key_seed, DIGEST256_LEN);
+ APPEND(ptr, ntor_key_seed, DIGEST256_LEN);
APPEND(ptr, M_HSEXPAND, strlen(M_HSEXPAND));
tor_assert(ptr == kdf_input + sizeof(kdf_input));
/* Generate the keys */
xof = crypto_xof_new();
crypto_xof_add_bytes(xof, kdf_input, sizeof(kdf_input));
- crypto_xof_squeeze_bytes(xof, keys, sizeof(keys));
+ crypto_xof_squeeze_bytes(xof, keys_out, HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN);
crypto_xof_free(xof);
- /* Generate keys structure and assign keys to it */
- rend_circuit_keys = tor_malloc_zero(sizeof(hs_ntor_rend_circuit_keys_t));
- ptr = keys;
- memcpy(rend_circuit_keys->KH, ptr, DIGEST256_LEN);
- ptr += DIGEST256_LEN;;
- memcpy(rend_circuit_keys->Df, ptr, DIGEST256_LEN);
- ptr += DIGEST256_LEN;
- memcpy(rend_circuit_keys->Db, ptr, DIGEST256_LEN);
- ptr += DIGEST256_LEN;
- memcpy(rend_circuit_keys->Kf, ptr, CIPHER256_KEY_LEN);
- ptr += CIPHER256_KEY_LEN;
- memcpy(rend_circuit_keys->Kb, ptr, CIPHER256_KEY_LEN);
- ptr += CIPHER256_KEY_LEN;
- tor_assert(ptr == keys + sizeof(keys));
-
- return rend_circuit_keys;
+ return 0;
}
diff --git a/src/or/hs_ntor.h b/src/or/hs_ntor.h
index cd75f46a4c..d07bff8cf0 100644
--- a/src/or/hs_ntor.h
+++ b/src/or/hs_ntor.h
@@ -6,6 +6,10 @@
#include "or.h"
+/* Output length of KDF for key expansion */
+#define HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN \
+ (DIGEST256_LEN*2 + CIPHER256_KEY_LEN*2)
+
/* Key material needed to encode/decode INTRODUCE1 cells */
typedef struct {
/* Key used for encryption of encrypted INTRODUCE1 blob */
@@ -23,21 +27,6 @@ typedef struct {
uint8_t ntor_key_seed[DIGEST256_LEN];
} hs_ntor_rend_cell_keys_t;
-/* Key material resulting from key expansion as detailed in section "4.2.1. Key
- * expansion" of rend-spec-ng.txt. */
-typedef struct {
- /* Per-circuit key material used in ESTABLISH_INTRO cell */
- uint8_t KH[DIGEST256_LEN];
- /* Authentication key for outgoing RELAY cells */
- uint8_t Df[DIGEST256_LEN];
- /* Authentication key for incoming RELAY cells */
- uint8_t Db[DIGEST256_LEN];
- /* Encryption key for outgoing RELAY cells */
- uint8_t Kf[CIPHER256_KEY_LEN];
- /* Decryption key for incoming RELAY cells */
- uint8_t Kb[CIPHER256_KEY_LEN];
-} hs_ntor_rend_circuit_keys_t;
-
int hs_ntor_client_get_introduce1_keys(
const ed25519_public_key_t *intro_auth_pubkey,
const curve25519_public_key_t *intro_enc_pubkey,
@@ -66,8 +55,9 @@ int hs_ntor_service_get_rendezvous1_keys(
const curve25519_public_key_t *client_ephemeral_enc_pubkey,
hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys_out);
-hs_ntor_rend_circuit_keys_t *hs_ntor_circuit_key_expansion(
- const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys);
+int hs_ntor_circuit_key_expansion(const uint8_t *ntor_key_seed,
+ size_t seed_len,
+ uint8_t *keys_out, size_t keys_out_len);
int hs_ntor_client_rendezvous2_mac_is_good(
const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys,
diff --git a/src/or/hs_service.c b/src/or/hs_service.c
index 205ef11c92..5ff118222d 100644
--- a/src/or/hs_service.c
+++ b/src/or/hs_service.c
@@ -6,169 +6,3273 @@
* \brief Implement next generation hidden service functionality
**/
+#define HS_SERVICE_PRIVATE
+
#include "or.h"
+#include "circpathbias.h"
+#include "circuitbuild.h"
+#include "circuitlist.h"
+#include "circuituse.h"
+#include "config.h"
+#include "directory.h"
+#include "main.h"
+#include "networkstatus.h"
+#include "nodelist.h"
#include "relay.h"
#include "rendservice.h"
-#include "circuitlist.h"
-#include "circpathbias.h"
+#include "router.h"
+#include "routerkeys.h"
+#include "routerlist.h"
+#include "statefile.h"
+#include "hs_circuit.h"
+#include "hs_common.h"
+#include "hs_config.h"
+#include "hs_circuit.h"
+#include "hs_descriptor.h"
+#include "hs_ident.h"
#include "hs_intropoint.h"
#include "hs_service.h"
-#include "hs_common.h"
-#include "hs/cell_establish_intro.h"
+/* Trunnel */
+#include "ed25519_cert.h"
#include "hs/cell_common.h"
+#include "hs/cell_establish_intro.h"
+
+/* Helper macro. Iterate over every service in the global map. The var is the
+ * name of the service pointer. */
+#define FOR_EACH_SERVICE_BEGIN(var) \
+ STMT_BEGIN \
+ hs_service_t **var##_iter, *var; \
+ HT_FOREACH(var##_iter, hs_service_ht, hs_service_map) { \
+ var = *var##_iter;
+#define FOR_EACH_SERVICE_END } STMT_END ;
+
+/* Helper macro. Iterate over both current and previous descriptor of a
+ * service. The var is the name of the descriptor pointer. This macro skips
+ * any descriptor object of the service that is NULL. */
+#define FOR_EACH_DESCRIPTOR_BEGIN(service, var) \
+ STMT_BEGIN \
+ hs_service_descriptor_t *var; \
+ for (int var ## _loop_idx = 0; var ## _loop_idx < 2; \
+ ++var ## _loop_idx) { \
+ (var ## _loop_idx == 0) ? (var = service->desc_current) : \
+ (var = service->desc_next); \
+ if (var == NULL) continue;
+#define FOR_EACH_DESCRIPTOR_END } STMT_END ;
+
+/* Onion service directory file names. */
+static const char fname_keyfile_prefix[] = "hs_ed25519";
+static const char fname_hostname[] = "hostname";
+static const char address_tld[] = "onion";
+
+/* Staging list of service object. When configuring service, we add them to
+ * this list considered a staging area and they will get added to our global
+ * map once the keys have been loaded. These two steps are seperated because
+ * loading keys requires that we are an actual running tor process. */
+static smartlist_t *hs_service_staging_list;
+
+/** True if the list of available router descriptors might have changed which
+ * might result in an altered hash ring. Check if the hash ring changed and
+ * reupload if needed */
+static int consider_republishing_hs_descriptors = 0;
-/* XXX We don't currently use these functions, apart from generating unittest
- data. When we start implementing the service-side support for prop224 we
- should revisit these functions and use them. */
+static void set_descriptor_revision_counter(hs_descriptor_t *hs_desc);
-/** Given an ESTABLISH_INTRO <b>cell</b>, encode it and place its payload in
- * <b>buf_out</b> which has size <b>buf_out_len</b>. Return the number of
- * bytes written, or a negative integer if there was an error. */
-ssize_t
-get_establish_intro_payload(uint8_t *buf_out, size_t buf_out_len,
- const trn_cell_establish_intro_t *cell)
+/* Helper: Function to compare two objects in the service map. Return 1 if the
+ * two service have the same master public identity key. */
+static inline int
+hs_service_ht_eq(const hs_service_t *first, const hs_service_t *second)
{
- ssize_t bytes_used = 0;
+ tor_assert(first);
+ tor_assert(second);
+ /* Simple key compare. */
+ return ed25519_pubkey_eq(&first->keys.identity_pk,
+ &second->keys.identity_pk);
+}
+
+/* Helper: Function for the service hash table code below. The key used is the
+ * master public identity key which is ultimately the onion address. */
+static inline unsigned int
+hs_service_ht_hash(const hs_service_t *service)
+{
+ tor_assert(service);
+ return (unsigned int) siphash24g(service->keys.identity_pk.pubkey,
+ sizeof(service->keys.identity_pk.pubkey));
+}
+
+/* This is _the_ global hash map of hidden services which indexed the service
+ * contained in it by master public identity key which is roughly the onion
+ * address of the service. */
+static struct hs_service_ht *hs_service_map;
+
+/* Register the service hash table. */
+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. */
+
+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_)
- if (buf_out_len < RELAY_PAYLOAD_SIZE) {
+/* 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
+ * search query. If pk is NULL, then it will be set to zero indicating the
+ * hash table to compare the directory path instead. */
+STATIC hs_service_t *
+find_service(hs_service_ht *map, const ed25519_public_key_t *pk)
+{
+ hs_service_t dummy_service;
+ tor_assert(map);
+ tor_assert(pk);
+ memset(&dummy_service, 0, sizeof(dummy_service));
+ ed25519_pubkey_copy(&dummy_service.keys.identity_pk, pk);
+ return HT_FIND(hs_service_ht, map, &dummy_service);
+}
+
+/* Register the given service in the given map. If the service already exists
+ * in the map, -1 is returned. On success, 0 is returned and the service
+ * ownership has been transfered to the global map. */
+STATIC int
+register_service(hs_service_ht *map, hs_service_t *service)
+{
+ tor_assert(map);
+ tor_assert(service);
+ tor_assert(!ed25519_public_key_is_zero(&service->keys.identity_pk));
+
+ if (find_service(map, &service->keys.identity_pk)) {
+ /* Existing service with the same key. Do not register it. */
return -1;
}
+ /* Taking ownership of the object at this point. */
+ HT_INSERT(hs_service_ht, map, service);
+ return 0;
+}
- bytes_used = trn_cell_establish_intro_encode(buf_out, buf_out_len,
- cell);
- return bytes_used;
+/* Remove a given service from the given map. If service is NULL or the
+ * service key is unset, return gracefully. */
+STATIC void
+remove_service(hs_service_ht *map, hs_service_t *service)
+{
+ hs_service_t *elm;
+
+ tor_assert(map);
+
+ /* Ignore if no service or key is zero. */
+ if (BUG(service == NULL) ||
+ BUG(ed25519_public_key_is_zero(&service->keys.identity_pk))) {
+ return;
+ }
+
+ elm = HT_REMOVE(hs_service_ht, map, service);
+ if (elm) {
+ tor_assert(elm == service);
+ } else {
+ log_warn(LD_BUG, "Could not find service in the global map "
+ "while removing service %s",
+ escaped(service->config.directory_path));
+ }
}
-/* Set the cell extensions of <b>cell</b>. */
+/* Set the default values for a service configuration object <b>c</b>. */
static void
-set_trn_cell_extensions(trn_cell_establish_intro_t *cell)
+set_service_default_config(hs_service_config_t *c,
+ const or_options_t *options)
+{
+ (void) options;
+ tor_assert(c);
+ c->ports = smartlist_new();
+ c->directory_path = NULL;
+ c->max_streams_per_rdv_circuit = 0;
+ c->max_streams_close_circuit = 0;
+ c->num_intro_points = NUM_INTRO_POINTS_DEFAULT;
+ c->allow_unknown_ports = 0;
+ c->is_single_onion = 0;
+ c->dir_group_readable = 0;
+ c->is_ephemeral = 0;
+}
+
+/* From a service configuration object config, clear everything from it
+ * meaning free allocated pointers and reset the values. */
+static void
+service_clear_config(hs_service_config_t *config)
+{
+ if (config == NULL) {
+ return;
+ }
+ tor_free(config->directory_path);
+ if (config->ports) {
+ SMARTLIST_FOREACH(config->ports, rend_service_port_config_t *, p,
+ rend_service_port_config_free(p););
+ smartlist_free(config->ports);
+ }
+ memset(config, 0, sizeof(*config));
+}
+
+/* Return the lower bound of maximum INTRODUCE2 cells per circuit before we
+ * rotate intro point (defined by a consensus parameter or the default
+ * value). */
+static int32_t
+get_intro_point_min_introduce2(void)
{
- trn_cell_extension_t *trn_cell_extensions = trn_cell_extension_new();
+ /* The [0, 2147483647] range is quite large to accomodate anything we decide
+ * in the future. */
+ return networkstatus_get_param(NULL, "hs_intro_min_introduce2",
+ INTRO_POINT_MIN_LIFETIME_INTRODUCTIONS,
+ 0, INT32_MAX);
+}
- /* For now, we don't use extensions at all. */
- trn_cell_extensions->num = 0; /* It's already zeroed, but be explicit. */
- trn_cell_establish_intro_set_extensions(cell, trn_cell_extensions);
+/* Return the upper bound of maximum INTRODUCE2 cells per circuit before we
+ * rotate intro point (defined by a consensus parameter or the default
+ * value). */
+static int32_t
+get_intro_point_max_introduce2(void)
+{
+ /* The [0, 2147483647] range is quite large to accomodate anything we decide
+ * in the future. */
+ return networkstatus_get_param(NULL, "hs_intro_max_introduce2",
+ INTRO_POINT_MAX_LIFETIME_INTRODUCTIONS,
+ 0, INT32_MAX);
}
-/** Given the circuit handshake info in <b>circuit_key_material</b>, create and
- * return an ESTABLISH_INTRO cell. Return NULL if something went wrong. The
- * returned cell is allocated on the heap and it's the responsibility of the
- * caller to free it. */
-trn_cell_establish_intro_t *
-generate_establish_intro_cell(const uint8_t *circuit_key_material,
- size_t circuit_key_material_len)
+/* Return the minimum lifetime in seconds of an introduction point defined by a
+ * consensus parameter or the default value. */
+static int32_t
+get_intro_point_min_lifetime(void)
{
- trn_cell_establish_intro_t *cell = NULL;
- ssize_t encoded_len;
+#define MIN_INTRO_POINT_LIFETIME_TESTING 10
+ if (get_options()->TestingTorNetwork) {
+ return MIN_INTRO_POINT_LIFETIME_TESTING;
+ }
- log_warn(LD_GENERAL,
- "Generating ESTABLISH_INTRO cell (key_material_len: %u)",
- (unsigned) circuit_key_material_len);
+ /* The [0, 2147483647] range is quite large to accomodate anything we decide
+ * in the future. */
+ return networkstatus_get_param(NULL, "hs_intro_min_lifetime",
+ INTRO_POINT_LIFETIME_MIN_SECONDS,
+ 0, INT32_MAX);
+}
- /* Generate short-term keypair for use in ESTABLISH_INTRO */
- ed25519_keypair_t key_struct;
- if (ed25519_keypair_generate(&key_struct, 0) < 0) {
- goto err;
+/* Return the maximum lifetime in seconds of an introduction point defined by a
+ * consensus parameter or the default value. */
+static int32_t
+get_intro_point_max_lifetime(void)
+{
+#define MAX_INTRO_POINT_LIFETIME_TESTING 30
+ if (get_options()->TestingTorNetwork) {
+ return MAX_INTRO_POINT_LIFETIME_TESTING;
+ }
+
+ /* The [0, 2147483647] range is quite large to accomodate anything we decide
+ * in the future. */
+ return networkstatus_get_param(NULL, "hs_intro_max_lifetime",
+ INTRO_POINT_LIFETIME_MAX_SECONDS,
+ 0, INT32_MAX);
+}
+
+/* Return the number of extra introduction point defined by a consensus
+ * parameter or the default value. */
+static int32_t
+get_intro_point_num_extra(void)
+{
+ /* The [0, 128] range bounds the number of extra introduction point allowed.
+ * Above 128 intro points, it's getting a bit crazy. */
+ return networkstatus_get_param(NULL, "hs_intro_num_extra",
+ NUM_INTRO_POINTS_EXTRA, 0, 128);
+}
+
+/* Helper: Function that needs to return 1 for the HT for each loop which
+ * frees every service in an hash map. */
+static int
+ht_free_service_(struct hs_service_t *service, void *data)
+{
+ (void) data;
+ hs_service_free(service);
+ /* This function MUST return 1 so the given object is then removed from the
+ * service map leading to this free of the object being safe. */
+ return 1;
+}
+
+/* Free every service that can be found in the global map. Once done, clear
+ * and free the global map. */
+static void
+service_free_all(void)
+{
+ if (hs_service_map) {
+ /* The free helper function returns 1 so this is safe. */
+ hs_service_ht_HT_FOREACH_FN(hs_service_map, ht_free_service_, NULL);
+ HT_CLEAR(hs_service_ht, hs_service_map);
+ tor_free(hs_service_map);
+ hs_service_map = NULL;
}
- cell = trn_cell_establish_intro_new();
+ if (hs_service_staging_list) {
+ /* Cleanup staging list. */
+ SMARTLIST_FOREACH(hs_service_staging_list, hs_service_t *, s,
+ hs_service_free(s));
+ smartlist_free(hs_service_staging_list);
+ hs_service_staging_list = NULL;
+ }
+}
- /* Set AUTH_KEY_TYPE: 2 means ed25519 */
- trn_cell_establish_intro_set_auth_key_type(cell,
- HS_INTRO_AUTH_KEY_TYPE_ED25519);
+/* Free a given service intro point object. */
+STATIC void
+service_intro_point_free(hs_service_intro_point_t *ip)
+{
+ if (!ip) {
+ return;
+ }
+ memwipe(&ip->auth_key_kp, 0, sizeof(ip->auth_key_kp));
+ memwipe(&ip->enc_key_kp, 0, sizeof(ip->enc_key_kp));
+ crypto_pk_free(ip->legacy_key);
+ replaycache_free(ip->replay_cache);
+ hs_intropoint_clear(&ip->base);
+ tor_free(ip);
+}
- /* Set AUTH_KEY_LEN field */
- /* Must also set byte-length of AUTH_KEY to match */
- int auth_key_len = ED25519_PUBKEY_LEN;
- trn_cell_establish_intro_set_auth_key_len(cell, auth_key_len);
- trn_cell_establish_intro_setlen_auth_key(cell, auth_key_len);
+/* Helper: free an hs_service_intro_point_t object. This function is used by
+ * digest256map_free() which requires a void * pointer. */
+static void
+service_intro_point_free_(void *obj)
+{
+ service_intro_point_free(obj);
+}
- /* Set AUTH_KEY field */
- uint8_t *auth_key_ptr = trn_cell_establish_intro_getarray_auth_key(cell);
- memcpy(auth_key_ptr, key_struct.pubkey.pubkey, auth_key_len);
+/* Return a newly allocated service intro point and fully initialized from the
+ * given extend_info_t ei if non NULL. If is_legacy is true, we also generate
+ * the legacy key. On error, NULL is returned. */
+STATIC hs_service_intro_point_t *
+service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy)
+{
+ hs_desc_link_specifier_t *ls;
+ hs_service_intro_point_t *ip;
- /* No cell extensions needed */
- set_trn_cell_extensions(cell);
+ ip = tor_malloc_zero(sizeof(*ip));
+ /* We'll create the key material. No need for extra strong, those are short
+ * term keys. */
+ ed25519_keypair_generate(&ip->auth_key_kp, 0);
- /* Set signature size.
- We need to do this up here, because _encode() needs it and we need to call
- _encode() to calculate the MAC and signature.
- */
- int sig_len = ED25519_SIG_LEN;
- trn_cell_establish_intro_set_sig_len(cell, sig_len);
- trn_cell_establish_intro_setlen_sig(cell, sig_len);
+ { /* Set introduce2 max cells limit */
+ int32_t min_introduce2_cells = get_intro_point_min_introduce2();
+ int32_t max_introduce2_cells = get_intro_point_max_introduce2();
+ if (BUG(max_introduce2_cells < min_introduce2_cells)) {
+ goto err;
+ }
+ ip->introduce2_max = crypto_rand_int_range(min_introduce2_cells,
+ max_introduce2_cells);
+ }
+ { /* Set intro point lifetime */
+ int32_t intro_point_min_lifetime = get_intro_point_min_lifetime();
+ int32_t intro_point_max_lifetime = get_intro_point_max_lifetime();
+ if (BUG(intro_point_max_lifetime < intro_point_min_lifetime)) {
+ goto err;
+ }
+ ip->time_to_expire = time(NULL) +
+ crypto_rand_int_range(intro_point_min_lifetime,intro_point_max_lifetime);
+ }
- /* XXX How to make this process easier and nicer? */
+ ip->replay_cache = replaycache_new(0, 0);
- /* Calculate the cell MAC (aka HANDSHAKE_AUTH). */
- {
- /* To calculate HANDSHAKE_AUTH, we dump the cell in bytes, and then derive
- the MAC from it. */
- uint8_t cell_bytes_tmp[RELAY_PAYLOAD_SIZE] = {0};
- uint8_t mac[TRUNNEL_SHA3_256_LEN];
-
- encoded_len = trn_cell_establish_intro_encode(cell_bytes_tmp,
- sizeof(cell_bytes_tmp),
- cell);
- if (encoded_len < 0) {
- log_warn(LD_OR, "Unable to pre-encode ESTABLISH_INTRO cell.");
+ /* Initialize the base object. We don't need the certificate object. */
+ ip->base.link_specifiers = smartlist_new();
+
+ /* Generate the encryption key for this intro point. */
+ curve25519_keypair_generate(&ip->enc_key_kp, 0);
+ /* Figure out if this chosen node supports v3 or is legacy only. */
+ if (is_legacy) {
+ ip->base.is_only_legacy = 1;
+ /* Legacy mode that is doesn't support v3+ with ed25519 auth key. */
+ ip->legacy_key = crypto_pk_new();
+ if (crypto_pk_generate_key(ip->legacy_key) < 0) {
goto err;
}
+ }
+
+ if (ei == NULL) {
+ goto done;
+ }
+
+ /* We'll try to add all link specifier. Legacy, IPv4 and ed25519 are
+ * mandatory. */
+ ls = hs_desc_link_specifier_new(ei, LS_IPV4);
+ /* It is impossible to have an extend info object without a v4. */
+ if (BUG(!ls)) {
+ goto err;
+ }
+ smartlist_add(ip->base.link_specifiers, ls);
+
+ ls = hs_desc_link_specifier_new(ei, LS_LEGACY_ID);
+ /* It is impossible to have an extend info object without an identity
+ * digest. */
+ if (BUG(!ls)) {
+ goto err;
+ }
+ smartlist_add(ip->base.link_specifiers, ls);
+
+ /* ed25519 identity key is optional for intro points */
+ ls = hs_desc_link_specifier_new(ei, LS_ED25519_ID);
+ if (ls) {
+ smartlist_add(ip->base.link_specifiers, ls);
+ }
+
+ /* IPv6 is optional. */
+ ls = hs_desc_link_specifier_new(ei, LS_IPV6);
+ if (ls) {
+ smartlist_add(ip->base.link_specifiers, ls);
+ }
+
+ /* Finally, copy onion key from the extend_info_t object. */
+ memcpy(&ip->onion_key, &ei->curve25519_onion_key, sizeof(ip->onion_key));
+
+ done:
+ return ip;
+ err:
+ service_intro_point_free(ip);
+ return NULL;
+}
+
+/* Add the given intro point object to the given intro point map. The intro
+ * point MUST have its RSA encryption key set if this is a legacy type or the
+ * authentication key set otherwise. */
+STATIC void
+service_intro_point_add(digest256map_t *map, hs_service_intro_point_t *ip)
+{
+ hs_service_intro_point_t *old_ip_entry;
+
+ tor_assert(map);
+ tor_assert(ip);
+
+ old_ip_entry = digest256map_set(map, ip->auth_key_kp.pubkey.pubkey, ip);
+ /* Make sure we didn't just try to double-add an intro point */
+ tor_assert_nonfatal(!old_ip_entry);
+}
+
+/* For a given service, remove the intro point from that service's descriptors
+ * (check both current and next descriptor) */
+STATIC void
+service_intro_point_remove(const hs_service_t *service,
+ const hs_service_intro_point_t *ip)
+{
+ tor_assert(service);
+ tor_assert(ip);
+
+ /* Trying all descriptors. */
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ /* We'll try to remove the descriptor on both descriptors which is not
+ * very expensive to do instead of doing loopup + remove. */
+ digest256map_remove(desc->intro_points.map,
+ ip->auth_key_kp.pubkey.pubkey);
+ } FOR_EACH_DESCRIPTOR_END;
+}
+
+/* For a given service and authentication key, return the intro point or NULL
+ * if not found. This will check both descriptors in the service. */
+STATIC hs_service_intro_point_t *
+service_intro_point_find(const hs_service_t *service,
+ const ed25519_public_key_t *auth_key)
+{
+ hs_service_intro_point_t *ip = NULL;
+
+ tor_assert(service);
+ tor_assert(auth_key);
+
+ /* Trying all descriptors to find the right intro point.
+ *
+ * Even if we use the same node as intro point in both descriptors, the node
+ * will have a different intro auth key for each descriptor since we generate
+ * a new one everytime we pick an intro point.
+ *
+ * After #22893 gets implemented, intro points will be moved to be
+ * per-service instead of per-descriptor so this function will need to
+ * change.
+ */
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ if ((ip = digest256map_get(desc->intro_points.map,
+ auth_key->pubkey)) != NULL) {
+ break;
+ }
+ } FOR_EACH_DESCRIPTOR_END;
+
+ return ip;
+}
+
+/* For a given service and intro point, return the descriptor for which the
+ * intro point is assigned to. NULL is returned if not found. */
+STATIC hs_service_descriptor_t *
+service_desc_find_by_intro(const hs_service_t *service,
+ const hs_service_intro_point_t *ip)
+{
+ hs_service_descriptor_t *descp = NULL;
+
+ tor_assert(service);
+ tor_assert(ip);
+
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ if (digest256map_get(desc->intro_points.map,
+ ip->auth_key_kp.pubkey.pubkey)) {
+ descp = desc;
+ break;
+ }
+ } FOR_EACH_DESCRIPTOR_END;
+
+ return descp;
+}
+
+/* From a circuit identifier, get all the possible objects associated with the
+ * ident. If not NULL, service, ip or desc are set if the object can be found.
+ * They are untouched if they can't be found.
+ *
+ * This is an helper function because we do those lookups often so it's more
+ * convenient to simply call this functions to get all the things at once. */
+STATIC void
+get_objects_from_ident(const hs_ident_circuit_t *ident,
+ hs_service_t **service, hs_service_intro_point_t **ip,
+ hs_service_descriptor_t **desc)
+{
+ hs_service_t *s;
+
+ tor_assert(ident);
+
+ /* Get service object from the circuit identifier. */
+ s = find_service(hs_service_map, &ident->identity_pk);
+ if (s && service) {
+ *service = s;
+ }
+
+ /* From the service object, get the intro point object of that circuit. The
+ * following will query both descriptors intro points list. */
+ if (s && ip) {
+ *ip = service_intro_point_find(s, &ident->intro_auth_pk);
+ }
+
+ /* Get the descriptor for this introduction point and service. */
+ if (s && ip && *ip && desc) {
+ *desc = service_desc_find_by_intro(s, *ip);
+ }
+}
+
+/* From a given intro point, return the first link specifier of type
+ * encountered in the link specifier list. Return NULL if it can't be found.
+ *
+ * The caller does NOT have ownership of the object, the intro point does. */
+static hs_desc_link_specifier_t *
+get_link_spec_by_type(const hs_service_intro_point_t *ip, uint8_t type)
+{
+ hs_desc_link_specifier_t *lnk_spec = NULL;
+
+ tor_assert(ip);
+
+ SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers,
+ hs_desc_link_specifier_t *, ls) {
+ if (ls->type == type) {
+ lnk_spec = ls;
+ goto end;
+ }
+ } SMARTLIST_FOREACH_END(ls);
+
+ end:
+ return lnk_spec;
+}
+
+/* Given a service intro point, return the node_t associated to it. This can
+ * return NULL if the given intro point has no legacy ID or if the node can't
+ * be found in the consensus. */
+STATIC const node_t *
+get_node_from_intro_point(const hs_service_intro_point_t *ip)
+{
+ const hs_desc_link_specifier_t *ls;
+
+ tor_assert(ip);
+
+ ls = get_link_spec_by_type(ip, LS_LEGACY_ID);
+ if (BUG(!ls)) {
+ return NULL;
+ }
+ /* XXX In the future, we want to only use the ed25519 ID (#22173). */
+ return node_get_by_id((const char *) ls->u.legacy_id);
+}
+
+/* Given a service intro point, return the extend_info_t for it. This can
+ * return NULL if the node can't be found for the intro point or the extend
+ * info can't be created for the found node. If direct_conn is set, the extend
+ * info is validated on if we can connect directly. */
+static extend_info_t *
+get_extend_info_from_intro_point(const hs_service_intro_point_t *ip,
+ unsigned int direct_conn)
+{
+ extend_info_t *info = NULL;
+ const node_t *node;
+
+ tor_assert(ip);
+
+ node = get_node_from_intro_point(ip);
+ if (node == NULL) {
+ /* This can happen if the relay serving as intro point has been removed
+ * from the consensus. In that case, the intro point will be removed from
+ * the descriptor during the scheduled events. */
+ goto end;
+ }
+
+ /* In the case of a direct connection (single onion service), it is possible
+ * our firewall policy won't allow it so this can return a NULL value. */
+ info = extend_info_from_node(node, direct_conn);
+
+ end:
+ return info;
+}
+
+/* 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)
+{
+ unsigned int count = 0;
+
+ tor_assert(desc);
+
+ DIGEST256MAP_FOREACH(desc->intro_points.map, key,
+ const hs_service_intro_point_t *, ip) {
+ count += ip->circuit_established;
+ } DIGEST256MAP_FOREACH_END;
+
+ return count;
+}
+
+/* Close all rendezvous circuits for the given service. */
+static void
+close_service_rp_circuits(hs_service_t *service)
+{
+ origin_circuit_t *ocirc = NULL;
+
+ tor_assert(service);
+
+ /* The reason we go over all circuit instead of using the circuitmap API is
+ * because most hidden service circuits are rendezvous circuits so there is
+ * no real improvement at getting all rendezvous circuits from the
+ * circuitmap and then going over them all to find the right ones.
+ * Furthermore, another option would have been to keep a list of RP cookies
+ * for a service but it creates an engineering complexity since we don't
+ * have a "RP circuit closed" event to clean it up properly so we avoid a
+ * memory DoS possibility. */
+
+ while ((ocirc = circuit_get_next_service_rp_circ(ocirc))) {
+ /* Only close circuits that are v3 and for this service. */
+ if (ocirc->hs_ident != NULL &&
+ ed25519_pubkey_eq(&ocirc->hs_ident->identity_pk,
+ &service->keys.identity_pk)) {
+ /* Reason is FINISHED because service has been removed and thus the
+ * circuit is considered old/uneeded. When freed, it is removed from the
+ * hs circuitmap. */
+ circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED);
+ }
+ }
+}
+
+/* Close the circuit(s) for the given map of introduction points. */
+static void
+close_intro_circuits(hs_service_intropoints_t *intro_points)
+{
+ tor_assert(intro_points);
+
+ DIGEST256MAP_FOREACH(intro_points->map, key,
+ const hs_service_intro_point_t *, ip) {
+ origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip);
+ if (ocirc) {
+ /* Reason is FINISHED because service has been removed and thus the
+ * circuit is considered old/uneeded. When freed, the circuit is removed
+ * from the HS circuitmap. */
+ circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED);
+ }
+ } DIGEST256MAP_FOREACH_END;
+}
+
+/* Close all introduction circuits for the given service. */
+static void
+close_service_intro_circuits(hs_service_t *service)
+{
+ tor_assert(service);
+
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ close_intro_circuits(&desc->intro_points);
+ } FOR_EACH_DESCRIPTOR_END;
+}
+
+/* Close any circuits related to the given service. */
+static void
+close_service_circuits(hs_service_t *service)
+{
+ tor_assert(service);
+
+ /* Only support for version >= 3. */
+ if (BUG(service->config.version < HS_VERSION_THREE)) {
+ return;
+ }
+ /* Close intro points. */
+ close_service_intro_circuits(service);
+ /* Close rendezvous points. */
+ close_service_rp_circuits(service);
+}
+
+/* Move introduction points from the src descriptor to the dst descriptor. The
+ * destination service intropoints are wiped out if any before moving. */
+static void
+move_descriptor_intro_points(hs_service_descriptor_t *src,
+ hs_service_descriptor_t *dst)
+{
+ tor_assert(src);
+ tor_assert(dst);
+
+ digest256map_free(dst->intro_points.map, service_intro_point_free_);
+ dst->intro_points.map = src->intro_points.map;
+ /* Nullify the source. */
+ src->intro_points.map = NULL;
+}
+
+/* Move introduction points from the src service to the dst service. The
+ * destination service intropoints are wiped out if any before moving. */
+static void
+move_intro_points(hs_service_t *src, hs_service_t *dst)
+{
+ tor_assert(src);
+ tor_assert(dst);
+
+ if (src->desc_current && dst->desc_current) {
+ move_descriptor_intro_points(src->desc_current, dst->desc_current);
+ }
+ if (src->desc_next && dst->desc_next) {
+ move_descriptor_intro_points(src->desc_next, dst->desc_next);
+ }
+}
+
+/* Move every ephemeral services from the src service map to the dst service
+ * map. It is possible that a service can't be register to the dst map which
+ * won't stop the process of moving them all but will trigger a log warn. */
+static void
+move_ephemeral_services(hs_service_ht *src, hs_service_ht *dst)
+{
+ hs_service_t **iter, **next;
+
+ tor_assert(src);
+ tor_assert(dst);
+
+ /* Iterate over the map to find ephemeral service and move them to the other
+ * map. We loop using this method to have a safe removal process. */
+ for (iter = HT_START(hs_service_ht, src); iter != NULL; iter = next) {
+ hs_service_t *s = *iter;
+ if (!s->config.is_ephemeral) {
+ /* Yeah, we are in a very manual loop :). */
+ next = HT_NEXT(hs_service_ht, src, iter);
+ continue;
+ }
+ /* Remove service from map and then register to it to the other map.
+ * Reminder that "*iter" and "s" are the same thing. */
+ next = HT_NEXT_RMV(hs_service_ht, src, iter);
+ if (register_service(dst, s) < 0) {
+ log_warn(LD_BUG, "Ephemeral service key is already being used. "
+ "Skipping.");
+ }
+ }
+}
+
+/* Return a const string of the directory path escaped. If this is an
+ * ephemeral service, it returns "[EPHEMERAL]". This can only be called from
+ * the main thread because escaped() uses a static variable. */
+static const char *
+service_escaped_dir(const hs_service_t *s)
+{
+ return (s->config.is_ephemeral) ? "[EPHEMERAL]" :
+ escaped(s->config.directory_path);
+}
+
+/* Register services that are in the staging list. Once this function returns,
+ * the global service map will be set with the right content and all non
+ * surviving services will be cleaned up. */
+static void
+register_all_services(void)
+{
+ struct hs_service_ht *new_service_map;
+
+ tor_assert(hs_service_staging_list);
+
+ /* We'll save us some allocation and computing time. */
+ if (smartlist_len(hs_service_staging_list) == 0) {
+ return;
+ }
+
+ /* Allocate a new map that will replace the current one. */
+ new_service_map = tor_malloc_zero(sizeof(*new_service_map));
+ HT_INIT(hs_service_ht, new_service_map);
+
+ /* First step is to transfer all ephemeral services from the current global
+ * map to the new one we are constructing. We do not prune ephemeral
+ * services as the only way to kill them is by deleting it from the control
+ * port or stopping the tor daemon. */
+ move_ephemeral_services(hs_service_map, new_service_map);
+
+ SMARTLIST_FOREACH_BEGIN(hs_service_staging_list, hs_service_t *, snew) {
+ hs_service_t *s;
+
+ /* Check if that service is already in our global map and if so, we'll
+ * transfer the intro points to it. */
+ s = find_service(hs_service_map, &snew->keys.identity_pk);
+ if (s) {
+ /* Pass ownership of intro points from s (the current service) to snew
+ * (the newly configured one). */
+ move_intro_points(s, snew);
+ /* Remove the service from the global map because after this, we need to
+ * go over the remaining service in that map that aren't surviving the
+ * reload to close their circuits. */
+ remove_service(hs_service_map, s);
+ }
+ /* Great, this service is now ready to be added to our new map. */
+ if (BUG(register_service(new_service_map, snew) < 0)) {
+ /* This should never happen because prior to registration, we validate
+ * every service against the entire set. Not being able to register a
+ * service means we failed to validate correctly. In that case, don't
+ * break tor and ignore the service but tell user. */
+ log_warn(LD_BUG, "Unable to register service with directory %s",
+ service_escaped_dir(snew));
+ SMARTLIST_DEL_CURRENT(hs_service_staging_list, snew);
+ hs_service_free(snew);
+ }
+ } SMARTLIST_FOREACH_END(snew);
+
+ /* Close any circuits associated with the non surviving services. Every
+ * service in the current global map are roaming. */
+ FOR_EACH_SERVICE_BEGIN(service) {
+ close_service_circuits(service);
+ } FOR_EACH_SERVICE_END;
+
+ /* Time to make the switch. We'll clear the staging list because its content
+ * has now changed ownership to the map. */
+ smartlist_clear(hs_service_staging_list);
+ service_free_all();
+ hs_service_map = new_service_map;
+}
+
+/* Write the onion address of a given service to the given filename fname_ in
+ * the service directory. Return 0 on success else -1 on error. */
+STATIC int
+write_address_to_file(const hs_service_t *service, const char *fname_)
+{
+ int ret = -1;
+ char *fname = NULL;
+ char *addr_buf = NULL;
+
+ tor_assert(service);
+ tor_assert(fname_);
+
+ /* Construct the full address with the onion tld and write the hostname file
+ * to disk. */
+ tor_asprintf(&addr_buf, "%s.%s\n", service->onion_address, address_tld);
+ /* Notice here that we use the given "fname_". */
+ fname = hs_path_from_filename(service->config.directory_path, fname_);
+ if (write_str_to_file(fname, addr_buf, 0) < 0) {
+ log_warn(LD_REND, "Could not write onion address to hostname file %s",
+ escaped(fname));
+ goto end;
+ }
+
+#ifndef _WIN32
+ if (service->config.dir_group_readable) {
+ /* Mode to 0640. */
+ if (chmod(fname, S_IRUSR | S_IWUSR | S_IRGRP) < 0) {
+ log_warn(LD_FS, "Unable to make onion service hostname file %s "
+ "group-readable.", escaped(fname));
+ }
+ }
+#endif /* _WIN32 */
+
+ /* Success. */
+ ret = 0;
+ end:
+ tor_free(fname);
+ tor_free(addr_buf);
+ return ret;
+}
+
+/* Load and/or generate private keys for the given service. On success, the
+ * hostname file will be written to disk along with the master private key iff
+ * the service is not configured for offline keys. Return 0 on success else -1
+ * on failure. */
+static int
+load_service_keys(hs_service_t *service)
+{
+ int ret = -1;
+ char *fname = NULL;
+ ed25519_keypair_t *kp;
+ const hs_service_config_t *config;
+
+ tor_assert(service);
+
+ config = &service->config;
+
+ /* Create and fix permission on service directory. We are about to write
+ * files to that directory so make sure it exists and has the right
+ * permissions. We do this here because at this stage we know that Tor is
+ * actually running and the service we have has been validated. */
+ if (BUG(hs_check_service_private_dir(get_options()->User,
+ config->directory_path,
+ config->dir_group_readable, 1) < 0)) {
+ goto end;
+ }
+
+ /* Try to load the keys from file or generate it if not found. */
+ fname = hs_path_from_filename(config->directory_path, fname_keyfile_prefix);
+ /* Don't ask for key creation, we want to know if we were able to load it or
+ * we had to generate it. Better logging! */
+ kp = ed_key_init_from_file(fname, 0, LOG_INFO, NULL, 0, 0, 0, NULL);
+ if (!kp) {
+ log_info(LD_REND, "Unable to load keys from %s. Generating it...", fname);
+ /* We'll now try to generate the keys and for it we want the strongest
+ * randomness for it. The keypair will be written in different files. */
+ uint32_t key_flags = INIT_ED_KEY_CREATE | INIT_ED_KEY_EXTRA_STRONG |
+ INIT_ED_KEY_SPLIT;
+ kp = ed_key_init_from_file(fname, key_flags, LOG_WARN, NULL, 0, 0, 0,
+ NULL);
+ if (!kp) {
+ log_warn(LD_REND, "Unable to generate keys and save in %s.", fname);
+ goto end;
+ }
+ }
+
+ /* Copy loaded or generated keys to service object. */
+ ed25519_pubkey_copy(&service->keys.identity_pk, &kp->pubkey);
+ memcpy(&service->keys.identity_sk, &kp->seckey,
+ sizeof(service->keys.identity_sk));
+ /* This does a proper memory wipe. */
+ ed25519_keypair_free(kp);
+
+ /* Build onion address from the newly loaded keys. */
+ tor_assert(service->config.version <= UINT8_MAX);
+ hs_build_address(&service->keys.identity_pk,
+ (uint8_t) service->config.version,
+ service->onion_address);
+
+ /* Write onion address to hostname file. */
+ if (write_address_to_file(service, fname_hostname) < 0) {
+ goto end;
+ }
+
+ /* Succes. */
+ ret = 0;
+ end:
+ tor_free(fname);
+ return ret;
+}
+
+/* Free a given service descriptor object and all key material is wiped. */
+STATIC void
+service_descriptor_free(hs_service_descriptor_t *desc)
+{
+ if (!desc) {
+ return;
+ }
+ hs_descriptor_free(desc->desc);
+ memwipe(&desc->signing_kp, 0, sizeof(desc->signing_kp));
+ memwipe(&desc->blinded_kp, 0, sizeof(desc->blinded_kp));
+ SMARTLIST_FOREACH(desc->hsdir_missing_info, char *, id, tor_free(id));
+ smartlist_free(desc->hsdir_missing_info);
+ /* Cleanup all intro points. */
+ digest256map_free(desc->intro_points.map, service_intro_point_free_);
+ digestmap_free(desc->intro_points.failed_id, tor_free_);
+ if (desc->previous_hsdirs) {
+ SMARTLIST_FOREACH(desc->previous_hsdirs, char *, s, tor_free(s));
+ smartlist_free(desc->previous_hsdirs);
+ }
+ tor_free(desc);
+}
+
+/* Return a newly allocated service descriptor object. */
+STATIC hs_service_descriptor_t *
+service_descriptor_new(void)
+{
+ hs_service_descriptor_t *sdesc = tor_malloc_zero(sizeof(*sdesc));
+ sdesc->desc = tor_malloc_zero(sizeof(hs_descriptor_t));
+ /* Initialize the intro points map. */
+ sdesc->intro_points.map = digest256map_new();
+ sdesc->intro_points.failed_id = digestmap_new();
+ sdesc->hsdir_missing_info = smartlist_new();
+ sdesc->previous_hsdirs = smartlist_new();
+ return sdesc;
+}
+
+/* From the given service, remove all expired failing intro points for each
+ * descriptor. */
+static void
+remove_expired_failing_intro(hs_service_t *service, time_t now)
+{
+ tor_assert(service);
+
+ /* For both descriptors, cleanup the failing intro points list. */
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ DIGESTMAP_FOREACH_MODIFY(desc->intro_points.failed_id, key, time_t *, t) {
+ time_t failure_time = *t;
+ if ((failure_time + INTRO_CIRC_RETRY_PERIOD) <= now) {
+ MAP_DEL_CURRENT(key);
+ tor_free(t);
+ }
+ } DIGESTMAP_FOREACH_END;
+ } FOR_EACH_DESCRIPTOR_END;
+}
+
+/* For the given descriptor desc, put all node_t object found from its failing
+ * intro point list and put them in the given node_list. */
+static void
+setup_intro_point_exclude_list(const hs_service_descriptor_t *desc,
+ smartlist_t *node_list)
+{
+ tor_assert(desc);
+ tor_assert(node_list);
+
+ DIGESTMAP_FOREACH(desc->intro_points.failed_id, key, time_t *, t) {
+ (void) t; /* Make gcc happy. */
+ const node_t *node = node_get_by_id(key);
+ if (node) {
+ smartlist_add(node_list, (void *) node);
+ }
+ } DIGESTMAP_FOREACH_END;
+}
- /* sanity check */
- tor_assert(encoded_len > ED25519_SIG_LEN + 2 + TRUNNEL_SHA3_256_LEN);
+/* For the given failing intro point ip, we add its time of failure to the
+ * failed map and index it by identity digest (legacy ID) in the descriptor
+ * desc failed id map. */
+static void
+remember_failing_intro_point(const hs_service_intro_point_t *ip,
+ hs_service_descriptor_t *desc, time_t now)
+{
+ time_t *time_of_failure, *prev_ptr;
+ const hs_desc_link_specifier_t *legacy_ls;
+
+ tor_assert(ip);
+ tor_assert(desc);
+
+ time_of_failure = tor_malloc_zero(sizeof(time_t));
+ *time_of_failure = now;
+ legacy_ls = get_link_spec_by_type(ip, LS_LEGACY_ID);
+ tor_assert(legacy_ls);
+ prev_ptr = digestmap_set(desc->intro_points.failed_id,
+ (const char *) legacy_ls->u.legacy_id,
+ time_of_failure);
+ tor_free(prev_ptr);
+}
+
+/* Copy the descriptor link specifier object from src to dst. */
+static void
+link_specifier_copy(hs_desc_link_specifier_t *dst,
+ const hs_desc_link_specifier_t *src)
+{
+ tor_assert(dst);
+ tor_assert(src);
+ memcpy(dst, src, sizeof(hs_desc_link_specifier_t));
+}
+
+/* Using a given descriptor signing keypair signing_kp, a service intro point
+ * object ip and the time now, setup the content of an already allocated
+ * descriptor intro desc_ip.
+ *
+ * Return 0 on success else a negative value. */
+static int
+setup_desc_intro_point(const ed25519_keypair_t *signing_kp,
+ const hs_service_intro_point_t *ip,
+ time_t now, hs_desc_intro_point_t *desc_ip)
+{
+ int ret = -1;
+ time_t nearest_hour = now - (now % 3600);
- /* Calculate MAC of all fields before HANDSHAKE_AUTH */
- crypto_mac_sha3_256(mac, sizeof(mac),
- circuit_key_material, circuit_key_material_len,
- cell_bytes_tmp,
- encoded_len -
- (ED25519_SIG_LEN + 2 + TRUNNEL_SHA3_256_LEN));
- /* Write the MAC to the cell */
- uint8_t *handshake_ptr =
- trn_cell_establish_intro_getarray_handshake_mac(cell);
- memcpy(handshake_ptr, mac, sizeof(mac));
+ tor_assert(signing_kp);
+ tor_assert(ip);
+ tor_assert(desc_ip);
+
+ /* Copy the onion key. */
+ memcpy(&desc_ip->onion_key, &ip->onion_key, sizeof(desc_ip->onion_key));
+
+ /* Key and certificate material. */
+ desc_ip->auth_key_cert = tor_cert_create(signing_kp,
+ CERT_TYPE_AUTH_HS_IP_KEY,
+ &ip->auth_key_kp.pubkey,
+ nearest_hour,
+ HS_DESC_CERT_LIFETIME,
+ CERT_FLAG_INCLUDE_SIGNING_KEY);
+ if (desc_ip->auth_key_cert == NULL) {
+ log_warn(LD_REND, "Unable to create intro point auth-key certificate");
+ goto done;
}
- /* Calculate the cell signature */
+ /* Copy link specifier(s). */
+ SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers,
+ const hs_desc_link_specifier_t *, ls) {
+ hs_desc_link_specifier_t *dup = tor_malloc_zero(sizeof(*dup));
+ link_specifier_copy(dup, ls);
+ smartlist_add(desc_ip->link_specifiers, dup);
+ } SMARTLIST_FOREACH_END(ls);
+
+ /* For a legacy intro point, we'll use an RSA/ed cross certificate. */
+ if (ip->base.is_only_legacy) {
+ desc_ip->legacy.key = crypto_pk_dup_key(ip->legacy_key);
+ /* Create cross certification cert. */
+ ssize_t cert_len = tor_make_rsa_ed25519_crosscert(
+ &signing_kp->pubkey,
+ desc_ip->legacy.key,
+ nearest_hour + HS_DESC_CERT_LIFETIME,
+ &desc_ip->legacy.cert.encoded);
+ if (cert_len < 0) {
+ log_warn(LD_REND, "Unable to create enc key legacy cross cert.");
+ goto done;
+ }
+ desc_ip->legacy.cert.len = cert_len;
+ }
+
+ /* Encryption key and its cross certificate. */
{
- /* To calculate the sig we follow the same procedure as above. We first
- dump the cell up to the sig, and then calculate the sig */
- uint8_t cell_bytes_tmp[RELAY_PAYLOAD_SIZE] = {0};
- ed25519_signature_t sig;
-
- encoded_len = trn_cell_establish_intro_encode(cell_bytes_tmp,
- sizeof(cell_bytes_tmp),
- cell);
- if (encoded_len < 0) {
- log_warn(LD_OR, "Unable to pre-encode ESTABLISH_INTRO cell (2).");
- goto err;
+ ed25519_public_key_t ed25519_pubkey;
+
+ /* Use the public curve25519 key. */
+ memcpy(&desc_ip->enc_key, &ip->enc_key_kp.pubkey,
+ sizeof(desc_ip->enc_key));
+ /* The following can't fail. */
+ ed25519_public_key_from_curve25519_public_key(&ed25519_pubkey,
+ &ip->enc_key_kp.pubkey,
+ 0);
+ desc_ip->enc_key_cert = tor_cert_create(signing_kp,
+ CERT_TYPE_CROSS_HS_IP_KEYS,
+ &ed25519_pubkey, nearest_hour,
+ HS_DESC_CERT_LIFETIME,
+ CERT_FLAG_INCLUDE_SIGNING_KEY);
+ if (desc_ip->enc_key_cert == NULL) {
+ log_warn(LD_REND, "Unable to create enc key curve25519 cross cert.");
+ goto done;
}
+ }
+ /* Success. */
+ ret = 0;
- tor_assert(encoded_len > ED25519_SIG_LEN);
+ done:
+ return ret;
+}
- if (ed25519_sign_prefixed(&sig,
- cell_bytes_tmp,
- encoded_len -
- (ED25519_SIG_LEN + sizeof(cell->sig_len)),
- ESTABLISH_INTRO_SIG_PREFIX,
- &key_struct)) {
- log_warn(LD_BUG, "Unable to gen signature for ESTABLISH_INTRO cell.");
- goto err;
+/* Using the given descriptor from the given service, build the descriptor
+ * intro point list so we can then encode the descriptor for publication. This
+ * function does not pick intro points, they have to be in the descriptor
+ * current map. Cryptographic material (keys) must be initialized in the
+ * descriptor for this function to make sense. */
+static void
+build_desc_intro_points(const hs_service_t *service,
+ hs_service_descriptor_t *desc, time_t now)
+{
+ hs_desc_encrypted_data_t *encrypted;
+
+ tor_assert(service);
+ tor_assert(desc);
+
+ /* Ease our life. */
+ encrypted = &desc->desc->encrypted_data;
+ /* Cleanup intro points, we are about to set them from scratch. */
+ hs_descriptor_clear_intro_points(desc->desc);
+
+ DIGEST256MAP_FOREACH(desc->intro_points.map, key,
+ const hs_service_intro_point_t *, ip) {
+ hs_desc_intro_point_t *desc_ip = hs_desc_intro_point_new();
+ if (setup_desc_intro_point(&desc->signing_kp, ip, now, desc_ip) < 0) {
+ hs_desc_intro_point_free(desc_ip);
+ continue;
}
+ /* We have a valid descriptor intro point. Add it to the list. */
+ smartlist_add(encrypted->intro_points, desc_ip);
+ } DIGEST256MAP_FOREACH_END;
+}
+
+/* Populate the descriptor encrypted section fomr the given service object.
+ * This will generate a valid list of introduction points that can be used
+ * after for circuit creation. Return 0 on success else -1 on error. */
+static int
+build_service_desc_encrypted(const hs_service_t *service,
+ hs_service_descriptor_t *desc)
+{
+ hs_desc_encrypted_data_t *encrypted;
+
+ tor_assert(service);
+ tor_assert(desc);
+
+ encrypted = &desc->desc->encrypted_data;
+
+ encrypted->create2_ntor = 1;
+ encrypted->single_onion_service = service->config.is_single_onion;
+
+ /* Setup introduction points from what we have in the service. */
+ if (encrypted->intro_points == NULL) {
+ encrypted->intro_points = smartlist_new();
+ }
+ /* We do NOT build introduction point yet, we only do that once the circuit
+ * have been opened. Until we have the right number of introduction points,
+ * we do not encode anything in the descriptor. */
+
+ /* XXX: Support client authorization (#20700). */
+ encrypted->intro_auth_types = NULL;
+ return 0;
+}
+
+/* Populare the descriptor plaintext section from the given service object.
+ * The caller must make sure that the keys in the descriptors are valid that
+ * is are non-zero. Return 0 on success else -1 on error. */
+static int
+build_service_desc_plaintext(const hs_service_t *service,
+ hs_service_descriptor_t *desc, time_t now)
+{
+ int ret = -1;
+ hs_desc_plaintext_data_t *plaintext;
+
+ tor_assert(service);
+ tor_assert(desc);
+ /* XXX: Use a "assert_desc_ok()" ? */
+ tor_assert(!tor_mem_is_zero((char *) &desc->blinded_kp,
+ sizeof(desc->blinded_kp)));
+ tor_assert(!tor_mem_is_zero((char *) &desc->signing_kp,
+ sizeof(desc->signing_kp)));
- /* And write the signature to the cell */
- uint8_t *sig_ptr = trn_cell_establish_intro_getarray_sig(cell);
- memcpy(sig_ptr, sig.sig, sig_len);
+ /* Set the subcredential. */
+ hs_get_subcredential(&service->keys.identity_pk, &desc->blinded_kp.pubkey,
+ desc->desc->subcredential);
+
+ plaintext = &desc->desc->plaintext_data;
+
+ plaintext->version = service->config.version;
+ plaintext->lifetime_sec = HS_DESC_DEFAULT_LIFETIME;
+ plaintext->signing_key_cert =
+ tor_cert_create(&desc->blinded_kp, CERT_TYPE_SIGNING_HS_DESC,
+ &desc->signing_kp.pubkey, now, HS_DESC_CERT_LIFETIME,
+ CERT_FLAG_INCLUDE_SIGNING_KEY);
+ if (plaintext->signing_key_cert == NULL) {
+ log_warn(LD_REND, "Unable to create descriptor signing certificate for "
+ "service %s",
+ safe_str_client(service->onion_address));
+ goto end;
}
+ /* Copy public key material to go in the descriptor. */
+ ed25519_pubkey_copy(&plaintext->signing_pubkey, &desc->signing_kp.pubkey);
+ ed25519_pubkey_copy(&plaintext->blinded_pubkey, &desc->blinded_kp.pubkey);
+ /* Success. */
+ ret = 0;
+
+ end:
+ return ret;
+}
+
+/* For the given service and descriptor object, create the key material which
+ * is the blinded keypair and the descriptor signing keypair. Return 0 on
+ * success else -1 on error where the generated keys MUST be ignored. */
+static int
+build_service_desc_keys(const hs_service_t *service,
+ hs_service_descriptor_t *desc,
+ uint64_t time_period_num)
+{
+ int ret = 0;
+ ed25519_keypair_t kp;
+
+ tor_assert(desc);
+ tor_assert(!tor_mem_is_zero((char *) &service->keys.identity_pk,
+ ED25519_PUBKEY_LEN));
- /* We are done! Return the cell! */
- return cell;
+ /* XXX: Support offline key feature (#18098). */
+
+ /* Copy the identity keys to the keypair so we can use it to create the
+ * blinded key. */
+ memcpy(&kp.pubkey, &service->keys.identity_pk, sizeof(kp.pubkey));
+ memcpy(&kp.seckey, &service->keys.identity_sk, sizeof(kp.seckey));
+ /* Build blinded keypair for this time period. */
+ hs_build_blinded_keypair(&kp, NULL, 0, time_period_num, &desc->blinded_kp);
+ /* Let's not keep too much traces of our keys in memory. */
+ memwipe(&kp, 0, sizeof(kp));
+
+ /* No need for extra strong, this is a temporary key only for this
+ * descriptor. Nothing long term. */
+ if (ed25519_keypair_generate(&desc->signing_kp, 0) < 0) {
+ log_warn(LD_REND, "Can't generate descriptor signing keypair for "
+ "service %s",
+ safe_str_client(service->onion_address));
+ ret = -1;
+ }
+
+ return ret;
+}
+
+/* Given a service and the current time, build a descriptor for the service.
+ * This function does not pick introduction point, this needs to be done by
+ * the update function. On success, desc_out will point to the newly allocated
+ * descriptor object.
+ *
+ * This can error if we are unable to create keys or certificate. */
+static void
+build_service_descriptor(hs_service_t *service, time_t now,
+ uint64_t time_period_num,
+ hs_service_descriptor_t **desc_out)
+{
+ char *encoded_desc;
+ hs_service_descriptor_t *desc;
+
+ tor_assert(service);
+ tor_assert(desc_out);
+
+ desc = service_descriptor_new();
+ desc->time_period_num = time_period_num;
+
+ /* Create the needed keys so we can setup the descriptor content. */
+ if (build_service_desc_keys(service, desc, time_period_num) < 0) {
+ goto err;
+ }
+ /* Setup plaintext descriptor content. */
+ if (build_service_desc_plaintext(service, desc, now) < 0) {
+ goto err;
+ }
+ /* Setup encrypted descriptor content. */
+ if (build_service_desc_encrypted(service, desc) < 0) {
+ goto err;
+ }
+
+ /* Set the revision counter for this descriptor */
+ set_descriptor_revision_counter(desc->desc);
+
+ /* Let's make sure that we've created a descriptor that can actually be
+ * encoded properly. This function also checks if the encoded output is
+ * decodable after. */
+ if (BUG(hs_desc_encode_descriptor(desc->desc, &desc->signing_kp,
+ &encoded_desc) < 0)) {
+ goto err;
+ }
+ tor_free(encoded_desc);
+
+ /* Assign newly built descriptor to the next slot. */
+ *desc_out = desc;
+ return;
err:
- trn_cell_establish_intro_free(cell);
+ service_descriptor_free(desc);
+}
+
+/* Build descriptors for each service if needed. There are conditions to build
+ * a descriptor which are details in the function. */
+STATIC void
+build_all_descriptors(time_t now)
+{
+ FOR_EACH_SERVICE_BEGIN(service) {
+ if (service->desc_current == NULL) {
+ /* This means we just booted up because else this descriptor will never
+ * be NULL as it should always point to the descriptor that was in
+ * desc_next after rotation. */
+ build_service_descriptor(service, now, hs_get_time_period_num(now),
+ &service->desc_current);
+
+ log_info(LD_REND, "Hidden service %s current descriptor successfully "
+ "built. Now scheduled for upload.",
+ safe_str_client(service->onion_address));
+ }
+ /* A next descriptor to NULL indicate that we need to build a fresh one if
+ * we are in the overlap period for the _next_ time period since it means
+ * we either just booted or we just rotated our descriptors. */
+ if (hs_overlap_mode_is_active(NULL, now) && service->desc_next == NULL) {
+ build_service_descriptor(service, now, hs_get_next_time_period_num(now),
+ &service->desc_next);
+ log_info(LD_REND, "Hidden service %s next descriptor successfully "
+ "built. Now scheduled for upload.",
+ safe_str_client(service->onion_address));
+ }
+ } FOR_EACH_DESCRIPTOR_END;
+}
+
+/* Randomly pick a node to become an introduction point but not present in the
+ * given exclude_nodes list. The chosen node is put in the exclude list
+ * regardless of success or not because in case of failure, the node is simply
+ * unsusable from that point on. If direct_conn is set, try to pick a node
+ * that our local firewall/policy allows to directly connect to and if not,
+ * fallback to a normal 3-hop node. Return a newly allocated service intro
+ * point ready to be used for encoding. NULL on error. */
+static hs_service_intro_point_t *
+pick_intro_point(unsigned int direct_conn, smartlist_t *exclude_nodes)
+{
+ const node_t *node;
+ extend_info_t *info = NULL;
+ hs_service_intro_point_t *ip = NULL;
+ /* Normal 3-hop introduction point flags. */
+ router_crn_flags_t flags = CRN_NEED_UPTIME | CRN_NEED_DESC;
+ /* Single onion flags. */
+ router_crn_flags_t direct_flags = flags | CRN_PREF_ADDR | CRN_DIRECT_CONN;
+
+ node = router_choose_random_node(exclude_nodes, get_options()->ExcludeNodes,
+ direct_conn ? direct_flags : flags);
+ if (node == NULL && direct_conn) {
+ /* Unable to find a node for direct connection, let's fall back to a
+ * normal 3-hop node. */
+ node = router_choose_random_node(exclude_nodes,
+ get_options()->ExcludeNodes, flags);
+ }
+ if (!node) {
+ goto err;
+ }
+
+ /* We have a suitable node, add it to the exclude list. We do this *before*
+ * we can validate the extend information because even in case of failure,
+ * we don't want to use that node anymore. */
+ smartlist_add(exclude_nodes, (void *) node);
+
+ /* We do this to ease our life but also this call makes appropriate checks
+ * of the node object such as validating ntor support for instance. */
+ info = extend_info_from_node(node, direct_conn);
+ if (BUG(info == NULL)) {
+ goto err;
+ }
+
+ /* Let's do a basic sanity check here so that we don't end up advertising the
+ * ed25519 identity key of relays that don't actually support the link
+ * protocol */
+ if (!node_supports_ed25519_link_authentication(node)) {
+ tor_assert_nonfatal(ed25519_public_key_is_zero(&info->ed_identity));
+ }
+
+ /* Create our objects and populate them with the node information. */
+ ip = service_intro_point_new(info, !node_supports_ed25519_hs_intro(node));
+ if (ip == NULL) {
+ goto err;
+ }
+ extend_info_free(info);
+ return ip;
+ err:
+ service_intro_point_free(ip);
+ extend_info_free(info);
return NULL;
}
+/* For a given descriptor from the given service, pick any needed intro points
+ * and update the current map with those newly picked intro points. Return the
+ * number node that might have been added to the descriptor current map. */
+static unsigned int
+pick_needed_intro_points(hs_service_t *service,
+ hs_service_descriptor_t *desc)
+{
+ int i = 0, num_needed_ip;
+ smartlist_t *exclude_nodes = smartlist_new();
+
+ tor_assert(service);
+ tor_assert(desc);
+
+ /* Compute how many intro points we actually need to open. */
+ num_needed_ip = service->config.num_intro_points -
+ digest256map_size(desc->intro_points.map);
+ if (BUG(num_needed_ip < 0)) {
+ /* Let's not make tor freak out here and just skip this. */
+ goto done;
+ }
+
+ /* We want to end up with config.num_intro_points intro points, but if we
+ * have no intro points at all (chances are they all cycled or we are
+ * starting up), we launch get_intro_point_num_extra() extra circuits and
+ * use the first config.num_intro_points that complete. See proposal #155,
+ * section 4 for the rationale of this which is purely for performance.
+ *
+ * The ones after the first config.num_intro_points will be converted to
+ * 'General' internal circuits and then we'll drop them from the list of
+ * intro points. */
+ if (digest256map_size(desc->intro_points.map) == 0) {
+ num_needed_ip += get_intro_point_num_extra();
+ }
+
+ /* Build an exclude list of nodes of our intro point(s). The expiring intro
+ * points are OK to pick again because this is afterall a concept of round
+ * robin so they are considered valid nodes to pick again. */
+ DIGEST256MAP_FOREACH(desc->intro_points.map, key,
+ hs_service_intro_point_t *, ip) {
+ const node_t *intro_node = get_node_from_intro_point(ip);
+ if (intro_node) {
+ smartlist_add(exclude_nodes, (void*)intro_node);
+ }
+ } DIGEST256MAP_FOREACH_END;
+ /* Also, add the failing intro points that our descriptor encounteered in
+ * the exclude node list. */
+ setup_intro_point_exclude_list(desc, exclude_nodes);
+
+ for (i = 0; i < num_needed_ip; i++) {
+ hs_service_intro_point_t *ip;
+
+ /* This function will add the picked intro point node to the exclude nodes
+ * list so we don't pick the same one at the next iteration. */
+ ip = pick_intro_point(service->config.is_single_onion, exclude_nodes);
+ if (ip == NULL) {
+ /* If we end up unable to pick an introduction point it is because we
+ * can't find suitable node and calling this again is highly unlikely to
+ * give us a valid node all of the sudden. */
+ log_info(LD_REND, "Unable to find a suitable node to be an "
+ "introduction point for service %s.",
+ safe_str_client(service->onion_address));
+ goto done;
+ }
+ /* Valid intro point object, add it to the descriptor current map. */
+ service_intro_point_add(desc->intro_points.map, ip);
+ }
+ /* We've successfully picked all our needed intro points thus none are
+ * missing which will tell our upload process to expect the number of
+ * circuits to be the number of configured intro points circuits and not the
+ * number of intro points object that we have. */
+ desc->missing_intro_points = 0;
+
+ /* Success. */
+ done:
+ /* We don't have ownership of the node_t object in this list. */
+ smartlist_free(exclude_nodes);
+ return i;
+}
+
+/** Clear previous cached HSDirs in <b>desc</b>. */
+static void
+service_desc_clear_previous_hsdirs(hs_service_descriptor_t *desc)
+{
+ if (BUG(!desc->previous_hsdirs)) {
+ return;
+ }
+
+ SMARTLIST_FOREACH(desc->previous_hsdirs, char*, s, tor_free(s));
+ smartlist_clear(desc->previous_hsdirs);
+}
+
+/** Note that we attempted to upload <b>desc</b> to <b>hsdir</b>. */
+static void
+service_desc_note_upload(hs_service_descriptor_t *desc, const node_t *hsdir)
+{
+ char b64_digest[BASE64_DIGEST_LEN+1] = {0};
+ digest_to_base64(b64_digest, hsdir->identity);
+
+ if (BUG(!desc->previous_hsdirs)) {
+ return;
+ }
+
+ if (!smartlist_contains_string(desc->previous_hsdirs, b64_digest)) {
+ smartlist_add_strdup(desc->previous_hsdirs, b64_digest);
+ smartlist_sort_strings(desc->previous_hsdirs);
+ }
+}
+
+/** Schedule an upload of <b>desc</b>. If <b>descriptor_changed</b> is set, it
+ * means that this descriptor is dirty. */
+STATIC void
+service_desc_schedule_upload(hs_service_descriptor_t *desc,
+ time_t now,
+ int descriptor_changed)
+
+{
+ desc->next_upload_time = now;
+
+ /* If the descriptor changed, clean up the old HSDirs list. We want to
+ * re-upload no matter what. */
+ if (descriptor_changed) {
+ service_desc_clear_previous_hsdirs(desc);
+ }
+}
+
+/* Update the given descriptor from the given service. The possible update
+ * actions includes:
+ * - Picking missing intro points if needed.
+ * - Incrementing the revision counter if needed.
+ */
+static void
+update_service_descriptor(hs_service_t *service,
+ hs_service_descriptor_t *desc, time_t now)
+{
+ unsigned int num_intro_points;
+
+ tor_assert(service);
+ tor_assert(desc);
+ tor_assert(desc->desc);
+
+ num_intro_points = digest256map_size(desc->intro_points.map);
+
+ /* Pick any missing introduction point(s). */
+ if (num_intro_points < service->config.num_intro_points) {
+ unsigned int num_new_intro_points = pick_needed_intro_points(service,
+ desc);
+ if (num_new_intro_points != 0) {
+ log_info(LD_REND, "Service %s just picked %u intro points and wanted "
+ "%u. It currently has %d intro points. "
+ "Launching ESTABLISH_INTRO circuit shortly.",
+ safe_str_client(service->onion_address),
+ num_new_intro_points,
+ service->config.num_intro_points - num_intro_points,
+ num_intro_points);
+ /* We'll build those introduction point into the descriptor once we have
+ * confirmation that the circuits are opened and ready. However,
+ * indicate that this descriptor should be uploaded from now on. */
+ service_desc_schedule_upload(desc, now, 1);
+ }
+ /* Were we able to pick all the intro points we needed? If not, we'll
+ * flag the descriptor that it's missing intro points because it
+ * couldn't pick enough which will trigger a descriptor upload. */
+ if ((num_new_intro_points + num_intro_points) <
+ service->config.num_intro_points) {
+ desc->missing_intro_points = 1;
+ }
+ }
+}
+
+/* Update descriptors for each service if needed. */
+STATIC void
+update_all_descriptors(time_t now)
+{
+ FOR_EACH_SERVICE_BEGIN(service) {
+ /* We'll try to update each descriptor that is if certain conditions apply
+ * in order for the descriptor to be updated. */
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ update_service_descriptor(service, desc, now);
+ } FOR_EACH_DESCRIPTOR_END;
+ } FOR_EACH_SERVICE_END;
+}
+
+/* Return true iff the given intro point has expired that is it has been used
+ * for too long or we've reached our max seen INTRODUCE2 cell. */
+STATIC int
+intro_point_should_expire(const hs_service_intro_point_t *ip,
+ time_t now)
+{
+ tor_assert(ip);
+
+ if (ip->introduce2_count >= ip->introduce2_max) {
+ goto expired;
+ }
+
+ if (ip->time_to_expire <= now) {
+ goto expired;
+ }
+
+ /* Not expiring. */
+ return 0;
+ expired:
+ return 1;
+}
+
+/* Go over the given set of intro points for each service and remove any
+ * invalid ones. The conditions for removal are:
+ *
+ * - The node doesn't exists anymore (not in consensus)
+ * OR
+ * - The intro point maximum circuit retry count has been reached and no
+ * circuit can be found associated with it.
+ * OR
+ * - The intro point has expired and we should pick a new one.
+ *
+ * If an intro point is removed, the circuit (if any) is immediately close.
+ * If a circuit can't be found, the intro point is kept if it hasn't reached
+ * its maximum circuit retry value and thus should be retried. */
+static void
+cleanup_intro_points(hs_service_t *service, time_t now)
+{
+ tor_assert(service);
+
+ /* For both descriptors, cleanup the intro points. */
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ /* Go over the current intro points we have, make sure they are still
+ * valid and remove any of them that aren't. */
+ DIGEST256MAP_FOREACH_MODIFY(desc->intro_points.map, key,
+ hs_service_intro_point_t *, ip) {
+ const node_t *node = get_node_from_intro_point(ip);
+ origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip);
+ int has_expired = intro_point_should_expire(ip, now);
+
+ /* We cleanup an intro point if it has expired or if we do not know the
+ * node_t anymore (removed from our latest consensus) or if we've
+ * reached the maximum number of retry with a non existing circuit. */
+ if (has_expired || node == NULL ||
+ ip->circuit_retries > MAX_INTRO_POINT_CIRCUIT_RETRIES) {
+ /* Remove intro point from descriptor map. We'll add it to the failed
+ * map if we retried it too many times. */
+ MAP_DEL_CURRENT(key);
+ service_intro_point_free(ip);
+
+ /* XXX: Legacy code does NOT do that, it keeps the circuit open until
+ * a new descriptor is uploaded and then closed all expiring intro
+ * point circuit. Here, we close immediately and because we just
+ * discarded the intro point, a new one will be selected, a new
+ * descriptor created and uploaded. There is no difference to an
+ * attacker between the timing of a new consensus and intro point
+ * rotation (possibly?). */
+ if (ocirc && !TO_CIRCUIT(ocirc)->marked_for_close) {
+ /* After this, no new cells will be handled on the circuit. */
+ circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED);
+ }
+ continue;
+ }
+ } DIGEST256MAP_FOREACH_END;
+ } FOR_EACH_DESCRIPTOR_END;
+}
+
+/** We just entered overlap period and we need to rotate our <b>service</b>
+ * descriptors */
+static void
+rotate_service_descriptors(hs_service_t *service)
+{
+ if (service->desc_current) {
+ /* Close all IP circuits for the descriptor. */
+ close_intro_circuits(&service->desc_current->intro_points);
+ /* We don't need this one anymore, we won't serve any clients coming with
+ * this service descriptor. */
+ service_descriptor_free(service->desc_current);
+ }
+ /* The next one become the current one and emptying the next will trigger
+ * a descriptor creation for it. */
+ service->desc_current = service->desc_next;
+ service->desc_next = NULL;
+}
+
+/** Return true if <b>service</b> **just** entered overlap mode. */
+static int
+service_just_entered_overlap_mode(const hs_service_t *service,
+ int overlap_mode_is_active)
+{
+ if (overlap_mode_is_active && !service->state.in_overlap_period) {
+ return 1;
+ }
+
+ return 0;
+}
+
+/** Return true if <b>service</b> **just** left overlap mode. */
+static int
+service_just_left_overlap_mode(const hs_service_t *service,
+ int overlap_mode_is_active)
+{
+ if (!overlap_mode_is_active && service->state.in_overlap_period) {
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Rotate descriptors for each service if needed. If we are just entering or
+ * leaving the overlap period, rotate them that is point the previous
+ * descriptor to the current and cleanup the previous one. A non existing
+ * current descriptor will trigger a descriptor build for the next time
+ * period. */
+STATIC void
+rotate_all_descriptors(time_t now)
+{
+ /* XXX We rotate all our service descriptors at once. In the future it might
+ * be wise, to rotate service descriptors independently to hide that all
+ * those descriptors are on the same tor instance */
+
+ int overlap_mode_is_active = hs_overlap_mode_is_active(NULL, now);
+
+ FOR_EACH_SERVICE_BEGIN(service) {
+ int service_entered_overlap = service_just_entered_overlap_mode(service,
+ overlap_mode_is_active);
+ int service_left_overlap = service_just_left_overlap_mode(service,
+ overlap_mode_is_active);
+ /* This should not be possible */
+ if (BUG(service_entered_overlap && service_left_overlap)) {
+ return;
+ }
+
+ /* No changes in overlap mode: nothing to do here */
+ if (!service_entered_overlap && !service_left_overlap) {
+ return;
+ }
+
+ /* To get down there means that some change happened to overlap mode */
+ tor_assert(service_entered_overlap || service_left_overlap);
+
+ /* Update the overlap marks on this service */
+ if (service_entered_overlap) {
+ /* It's the first time the service encounters the overlap period so flag
+ * it in order to make sure we don't rotate at next check. */
+ service->state.in_overlap_period = 1;
+ } else if (service_left_overlap) {
+ service->state.in_overlap_period = 0;
+ }
+
+ if (service_entered_overlap) {
+ /* We just entered overlap period: recompute all HSDir indices. We need
+ * to do this otherwise nodes can get stuck with old HSDir indices until
+ * we fetch a new consensus, and we might need to reupload our desc
+ * before that. */
+ /* XXX find a better place than rotate_all_descriptors() to do this */
+ nodelist_recompute_all_hsdir_indices();
+ }
+
+ /* If we just entered or left overlap mode, rotate our descriptors */
+ log_info(LD_REND, "We just %s overlap period. About to rotate %s "
+ "descriptors (%p / %p).",
+ service_entered_overlap ? "entered" : "left",
+ safe_str_client(service->onion_address),
+ service->desc_current, service->desc_next);
+
+ /* If we have a next descriptor lined up, rotate the descriptors so that it
+ * becomes current. */
+ if (service->desc_next) {
+ rotate_service_descriptors(service);
+ }
+ } FOR_EACH_SERVICE_END;
+}
+
+/* Scheduled event run from the main loop. Make sure all our services are up
+ * to date and ready for the other scheduled events. This includes looking at
+ * the introduction points status and descriptor rotation time. */
+STATIC void
+run_housekeeping_event(time_t now)
+{
+ /* Note that nothing here opens circuit(s) nor uploads descriptor(s). We are
+ * simply moving things around or removing uneeded elements. */
+
+ FOR_EACH_SERVICE_BEGIN(service) {
+ /* Cleanup invalid intro points from the service descriptor. */
+ cleanup_intro_points(service, now);
+
+ /* Remove expired failing intro point from the descriptor failed list. We
+ * reset them at each INTRO_CIRC_RETRY_PERIOD. */
+ remove_expired_failing_intro(service, now);
+
+ /* At this point, the service is now ready to go through the scheduled
+ * events guaranteeing a valid state. Intro points might be missing from
+ * the descriptors after the cleanup but the update/build process will
+ * make sure we pick those missing ones. */
+ } FOR_EACH_SERVICE_END;
+}
+
+/* Scheduled event run from the main loop. Make sure all descriptors are up to
+ * date. Once this returns, each service descriptor needs to be considered for
+ * new introduction circuits and then for upload. */
+static void
+run_build_descriptor_event(time_t now)
+{
+ /* For v2 services, this step happens in the upload event. */
+
+ /* Run v3+ events. */
+ /* We start by rotating the descriptors only if needed. */
+ rotate_all_descriptors(now);
+
+ /* Then, we'll try to build new descriptors that we might need. The
+ * condition is that the next descriptor is non existing because it has
+ * been rotated or we just started up. */
+ build_all_descriptors(now);
+
+ /* Finally, we'll check if we should update the descriptors. Missing
+ * introduction points will be picked in this function which is useful for
+ * newly built descriptors. */
+ update_all_descriptors(now);
+}
+
+/* For the given service, launch any intro point circuits that could be
+ * needed. This considers every descriptor of the service. */
+static void
+launch_intro_point_circuits(hs_service_t *service)
+{
+ tor_assert(service);
+
+ /* For both descriptors, try to launch any missing introduction point
+ * circuits using the current map. */
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ /* Keep a ref on if we need a direct connection. We use this often. */
+ unsigned int direct_conn = service->config.is_single_onion;
+
+ DIGEST256MAP_FOREACH_MODIFY(desc->intro_points.map, key,
+ hs_service_intro_point_t *, ip) {
+ extend_info_t *ei;
+
+ /* Skip the intro point that already has an existing circuit
+ * (established or not). */
+ if (hs_circ_service_get_intro_circ(ip)) {
+ continue;
+ }
+
+ ei = get_extend_info_from_intro_point(ip, direct_conn);
+ if (ei == NULL) {
+ if (!direct_conn) {
+ /* In case of a multi-hop connection, it should never happen that we
+ * can't get the extend info from the node. Avoid connection and
+ * remove intro point from descriptor in order to recover from this
+ * potential bug. */
+ tor_assert_nonfatal(ei);
+ }
+ MAP_DEL_CURRENT(key);
+ service_intro_point_free(ip);
+ continue;
+ }
+
+ /* Launch a circuit to the intro point. */
+ ip->circuit_retries++;
+ if (hs_circ_launch_intro_point(service, ip, ei) < 0) {
+ log_warn(LD_REND, "Unable to launch intro circuit to node %s "
+ "for service %s.",
+ safe_str_client(extend_info_describe(ei)),
+ safe_str_client(service->onion_address));
+ /* Intro point will be retried if possible after this. */
+ }
+ extend_info_free(ei);
+ } DIGEST256MAP_FOREACH_END;
+ } FOR_EACH_DESCRIPTOR_END;
+}
+
+/* Don't try to build more than this many circuits before giving up for a
+ * while. Dynamically calculated based on the configured number of intro
+ * points for the given service and how many descriptor exists. The default
+ * use case of 3 introduction points and two descriptors will allow 28
+ * circuits for a retry period (((3 + 2) + (3 * 3)) * 2). */
+static unsigned int
+get_max_intro_circ_per_period(const hs_service_t *service)
+{
+ unsigned int count = 0;
+ unsigned int multiplier = 0;
+ unsigned int num_wanted_ip;
+
+ tor_assert(service);
+ tor_assert(service->config.num_intro_points <=
+ HS_CONFIG_V3_MAX_INTRO_POINTS);
+
+/* For a testing network, allow to do it for the maximum amount so circuit
+ * creation and rotation and so on can actually be tested without limit. */
+#define MAX_INTRO_POINT_CIRCUIT_RETRIES_TESTING -1
+ if (get_options()->TestingTorNetwork) {
+ return MAX_INTRO_POINT_CIRCUIT_RETRIES_TESTING;
+ }
+
+ num_wanted_ip = service->config.num_intro_points;
+
+ /* The calculation is as follow. We have a number of intro points that we
+ * want configured as a torrc option (num_intro_points). We then add an
+ * extra value so we can launch multiple circuits at once and pick the
+ * quickest ones. For instance, we want 3 intros, we add 2 extra so we'll
+ * pick 5 intros and launch 5 circuits. */
+ count += (num_wanted_ip + get_intro_point_num_extra());
+
+ /* Then we add the number of retries that is possible to do for each intro
+ * point. If we want 3 intros, we'll allow 3 times the number of possible
+ * retry. */
+ count += (num_wanted_ip * MAX_INTRO_POINT_CIRCUIT_RETRIES);
+
+ /* Then, we multiply by a factor of 2 if we have both descriptor or 0 if we
+ * have none. */
+ multiplier += (service->desc_current) ? 1 : 0;
+ multiplier += (service->desc_next) ? 1 : 0;
+
+ return (count * multiplier);
+}
+
+/* For the given service, return 1 if the service is allowed to launch more
+ * introduction circuits else 0 if the maximum has been reached for the retry
+ * period of INTRO_CIRC_RETRY_PERIOD. */
+STATIC int
+can_service_launch_intro_circuit(hs_service_t *service, time_t now)
+{
+ tor_assert(service);
+
+ /* Consider the intro circuit retry period of the service. */
+ if (now > (service->state.intro_circ_retry_started_time +
+ INTRO_CIRC_RETRY_PERIOD)) {
+ service->state.intro_circ_retry_started_time = now;
+ service->state.num_intro_circ_launched = 0;
+ goto allow;
+ }
+ /* Check if we can still launch more circuits in this period. */
+ if (service->state.num_intro_circ_launched <=
+ get_max_intro_circ_per_period(service)) {
+ goto allow;
+ }
+
+ /* Rate limit log that we've reached our circuit creation limit. */
+ {
+ char *msg;
+ time_t elapsed_time = now - service->state.intro_circ_retry_started_time;
+ static ratelim_t rlimit = RATELIM_INIT(INTRO_CIRC_RETRY_PERIOD);
+ if ((msg = rate_limit_log(&rlimit, now))) {
+ log_info(LD_REND, "Hidden service %s exceeded its circuit launch limit "
+ "of %u per %d seconds. It launched %u circuits in "
+ "the last %ld seconds. Will retry in %ld seconds.",
+ safe_str_client(service->onion_address),
+ get_max_intro_circ_per_period(service),
+ INTRO_CIRC_RETRY_PERIOD,
+ service->state.num_intro_circ_launched,
+ (long int) elapsed_time,
+ (long int) (INTRO_CIRC_RETRY_PERIOD - elapsed_time));
+ tor_free(msg);
+ }
+ }
+
+ /* Not allow. */
+ return 0;
+ allow:
+ return 1;
+}
+
+/* Scheduled event run from the main loop. Make sure we have all the circuits
+ * we need for each service. */
+static void
+run_build_circuit_event(time_t now)
+{
+ /* Make sure we can actually have enough information or able to build
+ * internal circuits as required by services. */
+ if (router_have_consensus_path() == CONSENSUS_PATH_UNKNOWN ||
+ !have_completed_a_circuit()) {
+ return;
+ }
+
+ /* Run v2 check. */
+ if (rend_num_services() > 0) {
+ rend_consider_services_intro_points(now);
+ }
+
+ /* Run v3+ check. */
+ FOR_EACH_SERVICE_BEGIN(service) {
+ /* For introduction circuit, we need to make sure we don't stress too much
+ * circuit creation so make sure this service is respecting that limit. */
+ if (can_service_launch_intro_circuit(service, now)) {
+ /* Launch intro point circuits if needed. */
+ launch_intro_point_circuits(service);
+ /* Once the circuits have opened, we'll make sure to update the
+ * descriptor intro point list and cleanup any extraneous. */
+ }
+ } FOR_EACH_SERVICE_END;
+}
+
+/* Encode and sign the service descriptor desc and upload it to the given
+ * hidden service directory. This does nothing if PublishHidServDescriptors
+ * is false. */
+static void
+upload_descriptor_to_hsdir(const hs_service_t *service,
+ hs_service_descriptor_t *desc, const node_t *hsdir)
+{
+ char version_str[4] = {0}, *encoded_desc = NULL;
+ directory_request_t *dir_req;
+ hs_ident_dir_conn_t ident;
+
+ tor_assert(service);
+ tor_assert(desc);
+ tor_assert(hsdir);
+
+ memset(&ident, 0, sizeof(ident));
+
+ /* Let's avoid doing that if tor is configured to not publish. */
+ if (!get_options()->PublishHidServDescriptors) {
+ log_info(LD_REND, "Service %s not publishing descriptor. "
+ "PublishHidServDescriptors is set to 1.",
+ safe_str_client(service->onion_address));
+ goto end;
+ }
+
+ /* First of all, we'll encode the descriptor. This should NEVER fail but
+ * just in case, let's make sure we have an actual usable descriptor. */
+ if (BUG(hs_desc_encode_descriptor(desc->desc, &desc->signing_kp,
+ &encoded_desc) < 0)) {
+ goto end;
+ }
+
+ /* Setup the connection identifier. */
+ ed25519_pubkey_copy(&ident.identity_pk, &service->keys.identity_pk);
+ /* This is our resource when uploading which is used to construct the URL
+ * with the version number: "/tor/hs/<version>/publish". */
+ tor_snprintf(version_str, sizeof(version_str), "%u",
+ service->config.version);
+
+ /* Build the directory request for this HSDir. */
+ dir_req = directory_request_new(DIR_PURPOSE_UPLOAD_HSDESC);
+ directory_request_set_routerstatus(dir_req, hsdir->rs);
+ directory_request_set_indirection(dir_req, DIRIND_ANONYMOUS);
+ directory_request_set_resource(dir_req, version_str);
+ directory_request_set_payload(dir_req, encoded_desc,
+ strlen(encoded_desc));
+ /* The ident object is copied over the directory connection object once
+ * the directory request is initiated. */
+ directory_request_upload_set_hs_ident(dir_req, &ident);
+
+ /* Initiate the directory request to the hsdir.*/
+ directory_initiate_request(dir_req);
+ directory_request_free(dir_req);
+
+ /* Add this node to previous_hsdirs list */
+ service_desc_note_upload(desc, hsdir);
+
+ /* Logging so we know where it was sent. */
+ {
+ int is_next_desc = (service->desc_next == desc);
+ const uint8_t *index = (is_next_desc) ? hsdir->hsdir_index->next :
+ hsdir->hsdir_index->current;
+ log_info(LD_REND, "Service %s %s descriptor of revision %" PRIu64
+ " initiated upload request to %s with index %s",
+ safe_str_client(service->onion_address),
+ (is_next_desc) ? "next" : "current",
+ desc->desc->plaintext_data.revision_counter,
+ safe_str_client(node_describe(hsdir)),
+ safe_str_client(hex_str((const char *) index, 32)));
+ }
+
+ /* XXX: Inform control port of the upload event (#20699). */
+ end:
+ tor_free(encoded_desc);
+ return;
+}
+
+/** Return a newly-allocated string for our state file which contains revision
+ * counter information for <b>desc</b>. The format is:
+ *
+ * HidServRevCounter <blinded_pubkey> <rev_counter>
+ */
+STATIC char *
+encode_desc_rev_counter_for_state(const hs_service_descriptor_t *desc)
+{
+ char *state_str = NULL;
+ char blinded_pubkey_b64[ED25519_BASE64_LEN+1];
+ uint64_t rev_counter = desc->desc->plaintext_data.revision_counter;
+ const ed25519_public_key_t *blinded_pubkey = &desc->blinded_kp.pubkey;
+
+ /* Turn the blinded key into b64 so that we save it on state */
+ tor_assert(blinded_pubkey);
+ if (ed25519_public_to_base64(blinded_pubkey_b64, blinded_pubkey) < 0) {
+ goto done;
+ }
+
+ /* Format is: <blinded key> <rev counter> */
+ tor_asprintf(&state_str, "%s %" PRIu64, blinded_pubkey_b64, rev_counter);
+
+ log_info(LD_GENERAL, "[!] Adding rev counter %" PRIu64 " for %s!",
+ rev_counter, blinded_pubkey_b64);
+
+ done:
+ return state_str;
+}
+
+/** Update HS descriptor revision counters in our state by removing the old
+ * ones and writing down the ones that are currently active. */
+static void
+update_revision_counters_in_state(void)
+{
+ config_line_t *lines = NULL;
+ config_line_t **nextline = &lines;
+ or_state_t *state = get_or_state();
+
+ /* Prepare our state structure with the rev counters */
+ FOR_EACH_SERVICE_BEGIN(service) {
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ /* We don't want to save zero counters */
+ if (desc->desc->plaintext_data.revision_counter == 0) {
+ continue;
+ }
+
+ *nextline = tor_malloc_zero(sizeof(config_line_t));
+ (*nextline)->key = tor_strdup("HidServRevCounter");
+ (*nextline)->value = encode_desc_rev_counter_for_state(desc);
+ nextline = &(*nextline)->next;
+ } FOR_EACH_DESCRIPTOR_END;
+ } FOR_EACH_SERVICE_END;
+
+ /* Remove the old rev counters, and replace them with the new ones */
+ config_free_lines(state->HidServRevCounter);
+ state->HidServRevCounter = lines;
+
+ /* Set the state as dirty since we just edited it */
+ if (!get_options()->AvoidDiskWrites) {
+ or_state_mark_dirty(state, 0);
+ }
+}
+
+/** Scan the string <b>state_line</b> for the revision counter of the service
+ * with <b>blinded_pubkey</b>. Set <b>service_found_out</b> to True if the
+ * line is relevant to this service, and return the cached revision
+ * counter. Else set <b>service_found_out</b> to False. */
+STATIC uint64_t
+check_state_line_for_service_rev_counter(const char *state_line,
+ const ed25519_public_key_t *blinded_pubkey,
+ int *service_found_out)
+{
+ smartlist_t *items = NULL;
+ int ok;
+ ed25519_public_key_t pubkey_in_state;
+ uint64_t rev_counter = 0;
+
+ tor_assert(service_found_out);
+ tor_assert(state_line);
+ tor_assert(blinded_pubkey);
+
+ /* Assume that the line is not for this service */
+ *service_found_out = 0;
+
+ /* Start parsing the state line */
+ items = smartlist_new();
+ smartlist_split_string(items, state_line, NULL,
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1);
+ if (smartlist_len(items) < 2) {
+ log_warn(LD_GENERAL, "Incomplete rev counter line. Ignoring.");
+ goto done;
+ }
+
+ char *b64_key_str = smartlist_get(items, 0);
+ char *saved_rev_counter_str = smartlist_get(items, 1);
+
+ /* Parse blinded key to check if it's for this hidden service */
+ if (ed25519_public_from_base64(&pubkey_in_state, b64_key_str) < 0) {
+ log_warn(LD_GENERAL, "Unable to base64 key in revcount line. Ignoring.");
+ goto done;
+ }
+ /* State line not for this hidden service */
+ if (!ed25519_pubkey_eq(&pubkey_in_state, blinded_pubkey)) {
+ goto done;
+ }
+
+ rev_counter = tor_parse_uint64(saved_rev_counter_str,
+ 10, 0, UINT64_MAX, &ok, NULL);
+ if (!ok) {
+ log_warn(LD_GENERAL, "Unable to parse rev counter. Ignoring.");
+ goto done;
+ }
+
+ /* Since we got this far, the line was for this service */
+ *service_found_out = 1;
+
+ log_info(LD_GENERAL, "Found rev counter for %s: %" PRIu64,
+ b64_key_str, rev_counter);
+
+ done:
+ tor_assert(items);
+ SMARTLIST_FOREACH(items, char*, s, tor_free(s));
+ smartlist_free(items);
+
+ return rev_counter;
+}
+
+/** Dig into our state file and find the current revision counter for the
+ * service with blinded key <b>blinded_pubkey</b>. If no revision counter is
+ * found, return 0. */
+static uint64_t
+get_rev_counter_for_service(const ed25519_public_key_t *blinded_pubkey)
+{
+ or_state_t *state = get_or_state();
+ config_line_t *line;
+
+ /* Set default value for rev counters (if not found) to 0 */
+ uint64_t final_rev_counter = 0;
+
+ for (line = state->HidServRevCounter ; line ; line = line->next) {
+ int service_found = 0;
+ uint64_t rev_counter = 0;
+
+ tor_assert(!strcmp(line->key, "HidServRevCounter"));
+
+ /* Scan all the HidServRevCounter lines till we find the line for this
+ service: */
+ rev_counter = check_state_line_for_service_rev_counter(line->value,
+ blinded_pubkey,
+ &service_found);
+ if (service_found) {
+ final_rev_counter = rev_counter;
+ goto done;
+ }
+ }
+
+ done:
+ return final_rev_counter;
+}
+
+/** Update the value of the revision counter for <b>hs_desc</b> and save it on
+ our state file. */
+static void
+increment_descriptor_revision_counter(hs_descriptor_t *hs_desc)
+{
+ /* Find stored rev counter if it exists */
+ uint64_t rev_counter =
+ get_rev_counter_for_service(&hs_desc->plaintext_data.blinded_pubkey);
+
+ /* Increment the revision counter of <b>hs_desc</b> so the next update (which
+ * will trigger an upload) will have the right value. We do this at this
+ * stage to only do it once because a descriptor can have many updates before
+ * being uploaded. By doing it at upload, we are sure to only increment by 1
+ * and thus avoid leaking how many operations we made on the descriptor from
+ * the previous one before uploading. */
+ rev_counter++;
+ hs_desc->plaintext_data.revision_counter = rev_counter;
+
+ update_revision_counters_in_state();
+}
+
+/** Set the revision counter in <b>hs_desc</b>, using the state file to find
+ * the current counter value if it exists. */
+static void
+set_descriptor_revision_counter(hs_descriptor_t *hs_desc)
+{
+ /* Find stored rev counter if it exists */
+ uint64_t rev_counter =
+ get_rev_counter_for_service(&hs_desc->plaintext_data.blinded_pubkey);
+
+ hs_desc->plaintext_data.revision_counter = rev_counter;
+}
+
+/* Encode and sign the service descriptor desc and upload it to the
+ * responsible hidden service directories. If for_next_period is true, the set
+ * of directories are selected using the next hsdir_index. This does nothing
+ * if PublishHidServDescriptors is false. */
+STATIC void
+upload_descriptor_to_all(const hs_service_t *service,
+ hs_service_descriptor_t *desc, int for_next_period)
+{
+ smartlist_t *responsible_dirs = NULL;
+
+ tor_assert(service);
+ tor_assert(desc);
+
+ /* Get our list of responsible HSDir. */
+ responsible_dirs = smartlist_new();
+ /* The parameter 0 means that we aren't a client so tell the function to use
+ * the spread store consensus paremeter. */
+ hs_get_responsible_hsdirs(&desc->blinded_kp.pubkey, desc->time_period_num,
+ for_next_period, 0, responsible_dirs);
+
+ /* For each responsible HSDir we have, initiate an upload command. */
+ SMARTLIST_FOREACH_BEGIN(responsible_dirs, const routerstatus_t *,
+ hsdir_rs) {
+ const node_t *hsdir_node = node_get_by_id(hsdir_rs->identity_digest);
+ /* Getting responsible hsdir implies that the node_t object exists for the
+ * routerstatus_t found in the consensus else we have a problem. */
+ tor_assert(hsdir_node);
+ /* Do not upload to an HSDir we don't have a descriptor for. */
+ if (!node_has_descriptor(hsdir_node)) {
+ log_info(LD_REND, "Missing descriptor for HSDir %s. Not uploading "
+ "descriptor. We'll try later once we have it.",
+ safe_str_client(node_describe(hsdir_node)));
+ /* Once we get new directory information, this HSDir will be retried if
+ * we ever get the descriptor. */
+ smartlist_add(desc->hsdir_missing_info,
+ tor_memdup(hsdir_rs->identity_digest, DIGEST_LEN));
+ continue;
+ }
+
+ /* Upload this descriptor to the chosen directory. */
+ upload_descriptor_to_hsdir(service, desc, hsdir_node);
+ } SMARTLIST_FOREACH_END(hsdir_rs);
+
+ /* Set the next upload time for this descriptor. Even if we are configured
+ * to not upload, we still want to follow the right cycle of life for this
+ * descriptor. */
+ desc->next_upload_time =
+ (time(NULL) + crypto_rand_int_range(HS_SERVICE_NEXT_UPLOAD_TIME_MIN,
+ HS_SERVICE_NEXT_UPLOAD_TIME_MAX));
+ {
+ char fmt_next_time[ISO_TIME_LEN+1];
+ format_local_iso_time(fmt_next_time, desc->next_upload_time);
+ log_debug(LD_REND, "Service %s set to upload a descriptor at %s",
+ safe_str_client(service->onion_address), fmt_next_time);
+ }
+
+ /* Update the revision counter of this descriptor */
+ increment_descriptor_revision_counter(desc->desc);
+
+ smartlist_free(responsible_dirs);
+ return;
+}
+
+/** The set of HSDirs have changed: check if the change affects our descriptor
+ * HSDir placement, and if it does, reupload the desc. */
+STATIC int
+service_desc_hsdirs_changed(const hs_service_t *service,
+ const hs_service_descriptor_t *desc)
+{
+ int retval = 0;
+ smartlist_t *responsible_dirs = smartlist_new();
+ smartlist_t *b64_responsible_dirs = smartlist_new();
+
+ /* No desc upload has happened yet: it will happen eventually */
+ if (!desc->previous_hsdirs || !smartlist_len(desc->previous_hsdirs)) {
+ goto done;
+ }
+
+ /* Get list of responsible hsdirs */
+ hs_get_responsible_hsdirs(&desc->blinded_kp.pubkey, desc->time_period_num,
+ service->desc_next == desc, 0, responsible_dirs);
+
+ /* Make a second list with their b64ed identity digests, so that we can
+ * compare it with out previous list of hsdirs */
+ SMARTLIST_FOREACH_BEGIN(responsible_dirs, const routerstatus_t *, hsdir_rs) {
+ char b64_digest[BASE64_DIGEST_LEN+1] = {0};
+ digest_to_base64(b64_digest, hsdir_rs->identity_digest);
+ smartlist_add_strdup(b64_responsible_dirs, b64_digest);
+ } SMARTLIST_FOREACH_END(hsdir_rs);
+
+ /* Sort this new smartlist so that we can compare it with the other one */
+ smartlist_sort_strings(b64_responsible_dirs);
+
+ /* Check whether the set of HSDirs changed */
+ if (!smartlist_strings_eq(b64_responsible_dirs, desc->previous_hsdirs)) {
+ log_info(LD_GENERAL, "Received new dirinfo and set of hsdirs changed!");
+ retval = 1;
+ } else {
+ log_debug(LD_GENERAL, "No change in hsdir set!");
+ }
+
+ done:
+ smartlist_free(responsible_dirs);
+
+ SMARTLIST_FOREACH(b64_responsible_dirs, char*, s, tor_free(s));
+ smartlist_free(b64_responsible_dirs);
+
+ return retval;
+}
+
+/* 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;
+
+ tor_assert(service);
+ tor_assert(desc);
+
+ /* If this descriptors has missing intro points that is that it couldn't get
+ * them all when it was time to pick them, it means that we should upload
+ * instead of waiting an arbitrary amount of time breaking the service.
+ * Else, if we have no missing intro points, we use the value taken from the
+ * service configuration. */
+ if (desc->missing_intro_points) {
+ num_intro_points = digest256map_size(desc->intro_points.map);
+ } else {
+ num_intro_points = service->config.num_intro_points;
+ }
+
+ /* This means we tried to pick intro points but couldn't get any so do not
+ * 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) {
+ 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) {
+ goto cannot;
+ }
+
+ /* Is it the right time to upload? */
+ if (desc->next_upload_time > now) {
+ goto cannot;
+ }
+
+ /* Don't upload desc if we don't have a live consensus */
+ if (!networkstatus_get_live_consensus(now)) {
+ goto cannot;
+ }
+
+ /* Do we know enough router descriptors to have adequate vision of the HSDir
+ hash ring? */
+ if (!router_have_minimum_dir_info()) {
+ goto cannot;
+ }
+
+ /* Can upload! */
+ return 1;
+ cannot:
+ return 0;
+}
+
+/* Scheduled event run from the main loop. Try to upload the descriptor for
+ * each service. */
+STATIC void
+run_upload_descriptor_event(time_t now)
+{
+ /* v2 services use the same function for descriptor creation and upload so
+ * we do everything here because the intro circuits were checked before. */
+ if (rend_num_services() > 0) {
+ rend_consider_services_upload(now);
+ rend_consider_descriptor_republication();
+ }
+
+ /* Run v3+ check. */
+ FOR_EACH_SERVICE_BEGIN(service) {
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ int for_next_period = 0;
+
+ /* If we were asked to re-examine the hash ring, and it changed, then
+ schedule an upload */
+ if (consider_republishing_hs_descriptors &&
+ service_desc_hsdirs_changed(service, desc)) {
+ service_desc_schedule_upload(desc, now, 0);
+ }
+
+ /* Can this descriptor be uploaded? */
+ if (!should_service_upload_descriptor(service, desc, now)) {
+ continue;
+ }
+
+ log_info(LD_REND, "Initiating upload for hidden service %s descriptor "
+ "for service %s with %u/%u introduction points%s.",
+ (desc == service->desc_current) ? "current" : "next",
+ safe_str_client(service->onion_address),
+ digest256map_size(desc->intro_points.map),
+ service->config.num_intro_points,
+ (desc->missing_intro_points) ? " (couldn't pick more)" : "");
+
+ /* At this point, we have to upload the descriptor so start by building
+ * the intro points descriptor section which we are now sure to be
+ * accurate because all circuits have been established. */
+ build_desc_intro_points(service, desc, now);
+
+ /* If the service is in the overlap period and this descriptor is the
+ * next one, it has to be uploaded for the next time period meaning
+ * we'll use the next node_t hsdir_index to pick the HSDirs. */
+ if (desc == service->desc_next) {
+ for_next_period = 1;
+ }
+ upload_descriptor_to_all(service, desc, for_next_period);
+ } FOR_EACH_DESCRIPTOR_END;
+ } FOR_EACH_SERVICE_END;
+
+ /* We are done considering whether to republish rend descriptors */
+ consider_republishing_hs_descriptors = 0;
+}
+
+/* Called when the introduction point circuit is done building and ready to be
+ * used. */
+static void
+service_intro_circ_has_opened(origin_circuit_t *circ)
+{
+ hs_service_t *service = NULL;
+ hs_service_intro_point_t *ip = NULL;
+ hs_service_descriptor_t *desc = NULL;
+
+ tor_assert(circ);
+
+ /* Let's do some basic sanity checking of the circ state */
+ if (BUG(!circ->cpath)) {
+ return;
+ }
+ if (BUG(TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO)) {
+ return;
+ }
+ if (BUG(!circ->hs_ident)) {
+ return;
+ }
+
+ /* Get the corresponding service and intro point. */
+ get_objects_from_ident(circ->hs_ident, &service, &ip, &desc);
+
+ if (service == NULL) {
+ log_warn(LD_REND, "Unknown service identity key %s on the introduction "
+ "circuit %u. Can't find onion service.",
+ safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)),
+ TO_CIRCUIT(circ)->n_circ_id);
+ goto err;
+ }
+ if (ip == NULL) {
+ log_warn(LD_REND, "Unknown introduction point auth key on circuit %u "
+ "for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto err;
+ }
+ /* We can't have an IP object without a descriptor. */
+ tor_assert(desc);
+
+ if (hs_circ_service_intro_has_opened(service, ip, desc, circ)) {
+ /* Getting here means that the circuit has been re-purposed because we
+ * have enough intro circuit opened. Remove the IP from the service. */
+ service_intro_point_remove(service, ip);
+ service_intro_point_free(ip);
+ }
+
+ goto done;
+
+ err:
+ /* Close circuit, we can't use it. */
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NOSUCHSERVICE);
+ done:
+ return;
+}
+
+/* Called when a rendezvous circuit is done building and ready to be used. */
+static void
+service_rendezvous_circ_has_opened(origin_circuit_t *circ)
+{
+ hs_service_t *service = NULL;
+
+ tor_assert(circ);
+ tor_assert(circ->cpath);
+ /* Getting here means this is a v3 rendezvous circuit. */
+ tor_assert(circ->hs_ident);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
+
+ /* Declare the circuit dirty to avoid reuse, and for path-bias */
+ if (!TO_CIRCUIT(circ)->timestamp_dirty)
+ TO_CIRCUIT(circ)->timestamp_dirty = time(NULL);
+ pathbias_count_use_attempt(circ);
+
+ /* Get the corresponding service and intro point. */
+ get_objects_from_ident(circ->hs_ident, &service, NULL, NULL);
+ if (service == NULL) {
+ log_warn(LD_REND, "Unknown service identity key %s on the rendezvous "
+ "circuit %u with cookie %s. Can't find onion service.",
+ safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)),
+ TO_CIRCUIT(circ)->n_circ_id,
+ hex_str((const char *) circ->hs_ident->rendezvous_cookie,
+ REND_COOKIE_LEN));
+ goto err;
+ }
+
+ /* If the cell can't be sent, the circuit will be closed within this
+ * function. */
+ hs_circ_service_rp_has_opened(service, circ);
+ goto done;
+
+ err:
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NOSUCHSERVICE);
+ done:
+ return;
+}
+
+/* We've been expecting an INTRO_ESTABLISHED cell on this circuit and it just
+ * arrived. Handle the INTRO_ESTABLISHED cell arriving on the given
+ * introduction circuit. Return 0 on success else a negative value. */
+static int
+service_handle_intro_established(origin_circuit_t *circ,
+ const uint8_t *payload,
+ size_t payload_len)
+{
+ hs_service_t *service = NULL;
+ hs_service_intro_point_t *ip = NULL;
+
+ tor_assert(circ);
+ tor_assert(payload);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO);
+
+ /* We need the service and intro point for this cell. */
+ get_objects_from_ident(circ->hs_ident, &service, &ip, NULL);
+
+ /* Get service object from the circuit identifier. */
+ if (service == NULL) {
+ log_warn(LD_REND, "Unknown service identity key %s on the introduction "
+ "circuit %u. Can't find onion service.",
+ safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)),
+ TO_CIRCUIT(circ)->n_circ_id);
+ goto err;
+ }
+ if (ip == NULL) {
+ /* We don't recognize the key. */
+ log_warn(LD_REND, "Introduction circuit established without an intro "
+ "point object on circuit %u for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto err;
+ }
+
+ /* Try to parse the payload into a cell making sure we do actually have a
+ * valid cell. On success, the ip object and circuit purpose is updated to
+ * reflect the fact that the introduction circuit is established. */
+ if (hs_circ_handle_intro_established(service, ip, circ, payload,
+ payload_len) < 0) {
+ goto err;
+ }
+
+ /* Flag that we have an established circuit for this intro point. This value
+ * is what indicates the upload scheduled event if we are ready to build the
+ * intro point into the descriptor and upload. */
+ ip->circuit_established = 1;
+
+ log_info(LD_REND, "Successfully received an INTRO_ESTABLISHED cell "
+ "on circuit %u for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ return 0;
+
+ err:
+ return -1;
+}
+
+/* We just received an INTRODUCE2 cell on the established introduction circuit
+ * circ. Handle the cell and return 0 on success else a negative value. */
+static int
+service_handle_introduce2(origin_circuit_t *circ, const uint8_t *payload,
+ size_t payload_len)
+{
+ hs_service_t *service = NULL;
+ hs_service_intro_point_t *ip = NULL;
+ hs_service_descriptor_t *desc = NULL;
+
+ tor_assert(circ);
+ tor_assert(payload);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_INTRO);
+
+ /* We'll need every object associated with this circuit. */
+ get_objects_from_ident(circ->hs_ident, &service, &ip, &desc);
+
+ /* Get service object from the circuit identifier. */
+ if (service == NULL) {
+ log_warn(LD_BUG, "Unknown service identity key %s when handling "
+ "an INTRODUCE2 cell on circuit %u",
+ safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)),
+ TO_CIRCUIT(circ)->n_circ_id);
+ goto err;
+ }
+ if (ip == NULL) {
+ /* We don't recognize the key. */
+ log_warn(LD_BUG, "Unknown introduction auth key when handling "
+ "an INTRODUCE2 cell on circuit %u for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto err;
+ }
+ /* If we have an IP object, we MUST have a descriptor object. */
+ tor_assert(desc);
+
+ /* 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,
+ payload, payload_len) < 0) {
+ goto err;
+ }
+
+ return 0;
+ err:
+ return -1;
+}
+
+/* For a given service and a descriptor of that service, consider retrying to
+ * upload the descriptor to any directories from which we had missing
+ * information when originally tried to be uploaded. This is called when our
+ * directory information has changed. */
+static void
+consider_hsdir_upload_retry(const hs_service_t *service,
+ hs_service_descriptor_t *desc)
+{
+ smartlist_t *responsible_dirs = NULL;
+ smartlist_t *still_missing_dirs = NULL;
+
+ tor_assert(service);
+ tor_assert(desc);
+
+ responsible_dirs = smartlist_new();
+ still_missing_dirs = smartlist_new();
+
+ /* We first need to get responsible directories from the latest consensus so
+ * we can then make sure that the node that we were missing information for
+ * is still responsible for this descriptor. */
+ hs_get_responsible_hsdirs(&desc->blinded_kp.pubkey, desc->time_period_num,
+ service->desc_next == desc, 0, responsible_dirs);
+
+ SMARTLIST_FOREACH_BEGIN(responsible_dirs, const routerstatus_t *, rs) {
+ const node_t *node;
+ const char *id = rs->identity_digest;
+ if (!smartlist_contains_digest(desc->hsdir_missing_info, id)) {
+ continue;
+ }
+ /* We do need a node_t object and descriptor to perform an upload. If
+ * found, we remove the id from the missing dir list else we add it to the
+ * still missing dir list to keep track of id that are still missing. */
+ node = node_get_by_id(id);
+ if (node && node_has_descriptor(node)) {
+ upload_descriptor_to_hsdir(service, desc, node);
+ smartlist_remove(desc->hsdir_missing_info, id);
+ } else {
+ smartlist_add(still_missing_dirs, tor_memdup(id, DIGEST_LEN));
+ }
+ } SMARTLIST_FOREACH_END(rs);
+
+ /* Switch the still missing dir list with the current missing dir list in
+ * the descriptor. It is possible that the list ends up empty which is what
+ * we want if we have no more missing dir. */
+ SMARTLIST_FOREACH(desc->hsdir_missing_info, char *, id, tor_free(id));
+ smartlist_free(desc->hsdir_missing_info);
+ desc->hsdir_missing_info = still_missing_dirs;
+
+ /* No ownership of the routerstatus_t object in this list. */
+ smartlist_free(responsible_dirs);
+}
+
+/* Add to list every filename used by service. This is used by the sandbox
+ * subsystem. */
+static void
+service_add_fnames_to_list(const hs_service_t *service, smartlist_t *list)
+{
+ const char *s_dir;
+ char fname[128] = {0};
+
+ tor_assert(service);
+ tor_assert(list);
+
+ /* Ease our life. */
+ s_dir = service->config.directory_path;
+ /* The hostname file. */
+ smartlist_add(list, hs_path_from_filename(s_dir, fname_hostname));
+ /* The key files splitted in two. */
+ tor_snprintf(fname, sizeof(fname), "%s_secret_key", fname_keyfile_prefix);
+ smartlist_add(list, hs_path_from_filename(s_dir, fname));
+ tor_snprintf(fname, sizeof(fname), "%s_public_key", fname_keyfile_prefix);
+ smartlist_add(list, hs_path_from_filename(s_dir, fname));
+}
+
+/* ========== */
+/* Public API */
+/* ========== */
+
+/* We just received a new batch of descriptors which might affect the shape of
+ * the HSDir hash ring. Signal that we should reexamine the hash ring and
+ * re-upload our HS descriptors if needed. */
+void
+hs_hsdir_set_changed_consider_reupload(void)
+{
+ log_info(LD_REND, "New dirinfo arrived: consider reuploading descriptor");
+ consider_republishing_hs_descriptors = 1;
+}
+
+/* Return the number of service we have configured and usable. */
+unsigned int
+hs_service_get_num_services(void)
+{
+ if (hs_service_map == NULL) {
+ return 0;
+ }
+ return HT_SIZE(hs_service_map);
+}
+
+/* Called once an introduction circuit is closed. If the circuit doesn't have
+ * a v3 identifier, it is ignored. */
+void
+hs_service_intro_circ_has_closed(origin_circuit_t *circ)
+{
+ hs_service_t *service = NULL;
+ hs_service_intro_point_t *ip = NULL;
+ hs_service_descriptor_t *desc = NULL;
+
+ tor_assert(circ);
+
+ if (circ->hs_ident == NULL) {
+ /* This is not a v3 circuit, ignore. */
+ goto end;
+ }
+
+ get_objects_from_ident(circ->hs_ident, &service, &ip, &desc);
+ if (service == NULL) {
+ log_warn(LD_REND, "Unable to find any hidden service associated "
+ "identity key %s on intro circuit %u.",
+ ed25519_fmt(&circ->hs_ident->identity_pk),
+ TO_CIRCUIT(circ)->n_circ_id);
+ goto end;
+ }
+ if (ip == NULL) {
+ /* The introduction point object has already been removed probably by our
+ * cleanup process so ignore. */
+ goto end;
+ }
+ /* Can't have an intro point object without a descriptor. */
+ tor_assert(desc);
+
+ /* Circuit disappeared so make sure the intro point is updated. By
+ * keeping the object in the descriptor, we'll be able to retry. */
+ ip->circuit_established = 0;
+
+ /* We've retried too many times, remember it as a failed intro point so we
+ * don't pick it up again. It will be retried in INTRO_CIRC_RETRY_PERIOD
+ * seconds. */
+ if (ip->circuit_retries > MAX_INTRO_POINT_CIRCUIT_RETRIES) {
+ remember_failing_intro_point(ip, desc, approx_time());
+ service_intro_point_remove(service, ip);
+ service_intro_point_free(ip);
+ }
+
+ end:
+ return;
+}
+
+/* Given conn, a rendezvous edge connection acting as an exit stream, look up
+ * the hidden service for the circuit circ, and look up the port and address
+ * based on the connection port. Assign the actual connection address.
+ *
+ * Return 0 on success. Return -1 on failure and the caller should NOT close
+ * the circuit. Return -2 on failure and the caller MUST close the circuit for
+ * security reasons. */
+int
+hs_service_set_conn_addr_port(const origin_circuit_t *circ,
+ edge_connection_t *conn)
+{
+ hs_service_t *service = NULL;
+
+ tor_assert(circ);
+ tor_assert(conn);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_REND_JOINED);
+ tor_assert(circ->hs_ident);
+
+ get_objects_from_ident(circ->hs_ident, &service, NULL, NULL);
+
+ if (service == NULL) {
+ log_warn(LD_REND, "Unable to find any hidden service associated "
+ "identity key %s on rendezvous circuit %u.",
+ ed25519_fmt(&circ->hs_ident->identity_pk),
+ TO_CIRCUIT(circ)->n_circ_id);
+ /* We want the caller to close the circuit because it's not a valid
+ * service so no danger. Attempting to bruteforce the entire key space by
+ * opening circuits to learn which service is being hosted here is
+ * impractical. */
+ goto err_close;
+ }
+
+ /* Enforce the streams-per-circuit limit, and refuse to provide a mapping if
+ * this circuit will exceed the limit. */
+ if (service->config.max_streams_per_rdv_circuit > 0 &&
+ (circ->hs_ident->num_rdv_streams >=
+ service->config.max_streams_per_rdv_circuit)) {
+#define MAX_STREAM_WARN_INTERVAL 600
+ static struct ratelim_t stream_ratelim =
+ RATELIM_INIT(MAX_STREAM_WARN_INTERVAL);
+ log_fn_ratelim(&stream_ratelim, LOG_WARN, LD_REND,
+ "Maximum streams per circuit limit reached on "
+ "rendezvous circuit %u for service %s. Circuit has "
+ "%" PRIu64 " out of %" PRIu64 " streams. %s.",
+ TO_CIRCUIT(circ)->n_circ_id,
+ service->onion_address,
+ circ->hs_ident->num_rdv_streams,
+ service->config.max_streams_per_rdv_circuit,
+ service->config.max_streams_close_circuit ?
+ "Closing circuit" : "Ignoring open stream request");
+ if (service->config.max_streams_close_circuit) {
+ /* Service explicitly configured to close immediately. */
+ goto err_close;
+ }
+ /* Exceeding the limit makes tor silently ignore the stream creation
+ * request and keep the circuit open. */
+ goto err_no_close;
+ }
+
+ /* Find a virtual port of that service mathcing the one in the connection if
+ * succesful, set the address in the connection. */
+ if (hs_set_conn_addr_port(service->config.ports, conn) < 0) {
+ log_info(LD_REND, "No virtual port mapping exists for port %d for "
+ "hidden service %s.",
+ TO_CONN(conn)->port, service->onion_address);
+ if (service->config.allow_unknown_ports) {
+ /* Service explicitly allow connection to unknown ports so close right
+ * away because we do not care about port mapping. */
+ goto err_close;
+ }
+ /* If the service didn't explicitly allow it, we do NOT close the circuit
+ * here to raise the bar in terms of performance for port mapping. */
+ goto err_no_close;
+ }
+
+ /* Success. */
+ return 0;
+ err_close:
+ /* Indicate the caller that the circuit should be closed. */
+ return -2;
+ err_no_close:
+ /* Indicate the caller to NOT close the circuit. */
+ return -1;
+}
+
+/* Add to file_list every filename used by a configured hidden service, and to
+ * dir_list every directory path used by a configured hidden service. This is
+ * used by the sandbox subsystem to whitelist those. */
+void
+hs_service_lists_fnames_for_sandbox(smartlist_t *file_list,
+ smartlist_t *dir_list)
+{
+ tor_assert(file_list);
+ tor_assert(dir_list);
+
+ /* Add files and dirs for legacy services. */
+ rend_services_add_filenames_to_lists(file_list, dir_list);
+
+ /* Add files and dirs for v3+. */
+ FOR_EACH_SERVICE_BEGIN(service) {
+ /* Skip ephemeral service, they don't touch the disk. */
+ if (service->config.is_ephemeral) {
+ continue;
+ }
+ service_add_fnames_to_list(service, file_list);
+ smartlist_add_strdup(dir_list, service->config.directory_path);
+ } FOR_EACH_DESCRIPTOR_END;
+}
+
+/* Called when our internal view of the directory has changed. We might have
+ * new descriptors for hidden service directories that we didn't have before
+ * so try them if it's the case. */
+void
+hs_service_dir_info_changed(void)
+{
+ /* For each service we have, check every descriptor and consider retrying to
+ * upload it to directories that we might have had missing information
+ * previously that is missing a router descriptor. */
+ FOR_EACH_SERVICE_BEGIN(service) {
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ /* This cleans up the descriptor missing hsdir information list if a
+ * successful upload is made or if any of the directory aren't
+ * responsible anymore for the service descriptor. */
+ consider_hsdir_upload_retry(service, desc);
+ } FOR_EACH_DESCRIPTOR_END;
+ } FOR_EACH_SERVICE_END;
+}
+
+/* Called when we get an INTRODUCE2 cell on the circ. Respond to the cell and
+ * launch a circuit to the rendezvous point. */
+int
+hs_service_receive_introduce2(origin_circuit_t *circ, const uint8_t *payload,
+ size_t payload_len)
+{
+ int ret = -1;
+
+ tor_assert(circ);
+ tor_assert(payload);
+
+ /* Do some initial validation and logging before we parse the cell */
+ if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_INTRO) {
+ log_warn(LD_PROTOCOL, "Received an INTRODUCE2 cell on a "
+ "non introduction circuit of purpose %d",
+ TO_CIRCUIT(circ)->purpose);
+ goto done;
+ }
+
+ if (circ->hs_ident) {
+ ret = service_handle_introduce2(circ, payload, payload_len);
+ } else {
+ ret = rend_service_receive_introduction(circ, payload, payload_len);
+ }
+
+ done:
+ return ret;
+}
+
+/* Called when we get an INTRO_ESTABLISHED cell. Mark the circuit as an
+ * established introduction point. Return 0 on success else a negative value
+ * and the circuit is closed. */
+int
+hs_service_receive_intro_established(origin_circuit_t *circ,
+ const uint8_t *payload,
+ size_t payload_len)
+{
+ int ret = -1;
+
+ tor_assert(circ);
+ tor_assert(payload);
+
+ if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO) {
+ log_warn(LD_PROTOCOL, "Received an INTRO_ESTABLISHED cell on a "
+ "non introduction circuit of purpose %d",
+ TO_CIRCUIT(circ)->purpose);
+ goto err;
+ }
+
+ /* Handle both version. v2 uses rend_data and v3 uses the hs circuit
+ * identifier hs_ident. Can't be both. */
+ if (circ->hs_ident) {
+ ret = service_handle_intro_established(circ, payload, payload_len);
+ } else {
+ ret = rend_service_intro_established(circ, payload, payload_len);
+ }
+
+ if (ret < 0) {
+ goto err;
+ }
+ return 0;
+ err:
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
+ return -1;
+}
+
+/* Called when any kind of hidden service circuit is done building thus
+ * opened. This is the entry point from the circuit subsystem. */
+void
+hs_service_circuit_has_opened(origin_circuit_t *circ)
+{
+ tor_assert(circ);
+
+ /* Handle both version. v2 uses rend_data and v3 uses the hs circuit
+ * identifier hs_ident. Can't be both. */
+ switch (TO_CIRCUIT(circ)->purpose) {
+ case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO:
+ if (circ->hs_ident) {
+ service_intro_circ_has_opened(circ);
+ } else {
+ rend_service_intro_has_opened(circ);
+ }
+ break;
+ case CIRCUIT_PURPOSE_S_CONNECT_REND:
+ if (circ->hs_ident) {
+ service_rendezvous_circ_has_opened(circ);
+ } else {
+ rend_service_rendezvous_has_opened(circ);
+ }
+ break;
+ default:
+ tor_assert(0);
+ }
+}
+
+/* Load and/or generate keys for all onion services including the client
+ * authorization if any. Return 0 on success, -1 on failure. */
+int
+hs_service_load_all_keys(void)
+{
+ /* Load v2 service keys if we have v2. */
+ if (rend_num_services() != 0) {
+ if (rend_service_load_all_keys(NULL) < 0) {
+ goto err;
+ }
+ }
+
+ /* Load or/and generate them for v3+. */
+ SMARTLIST_FOREACH_BEGIN(hs_service_staging_list, hs_service_t *, service) {
+ /* Ignore ephemeral service, they already have their keys set. */
+ if (service->config.is_ephemeral) {
+ continue;
+ }
+ log_info(LD_REND, "Loading v3 onion service keys from %s",
+ service_escaped_dir(service));
+ if (load_service_keys(service) < 0) {
+ goto err;
+ }
+ /* XXX: Load/Generate client authorization keys. (#20700) */
+ } SMARTLIST_FOREACH_END(service);
+
+ /* Final step, the staging list contains service in a quiescent state that
+ * is ready to be used. Register them to the global map. Once this is over,
+ * the staging list will be cleaned up. */
+ register_all_services();
+
+ /* All keys have been loaded successfully. */
+ return 0;
+ err:
+ return -1;
+}
+
+/* Put all service object in the given service list. After this, the caller
+ * looses ownership of every elements in the list and responsible to free the
+ * list pointer. */
+void
+hs_service_stage_services(const smartlist_t *service_list)
+{
+ tor_assert(service_list);
+ /* This list is freed at registration time but this function can be called
+ * multiple time. */
+ if (hs_service_staging_list == NULL) {
+ hs_service_staging_list = smartlist_new();
+ }
+ /* Add all service object to our staging list. Caller is responsible for
+ * freeing the service_list. */
+ smartlist_add_all(hs_service_staging_list, service_list);
+}
+
+/* Allocate and initilize a service object. The service configuration will
+ * contain the default values. Return the newly allocated object pointer. This
+ * function can't fail. */
+hs_service_t *
+hs_service_new(const or_options_t *options)
+{
+ hs_service_t *service = tor_malloc_zero(sizeof(hs_service_t));
+ /* Set default configuration value. */
+ set_service_default_config(&service->config, options);
+ /* Set the default service version. */
+ service->config.version = HS_SERVICE_DEFAULT_VERSION;
+ /* Allocate the CLIENT_PK replay cache in service state. */
+ service->state.replay_cache_rend_cookie =
+ replaycache_new(REND_REPLAY_TIME_INTERVAL, REND_REPLAY_TIME_INTERVAL);
+ return service;
+}
+
+/* Free the given <b>service</b> object and all its content. This function
+ * also takes care of wiping service keys from memory. It is safe to pass a
+ * NULL pointer. */
+void
+hs_service_free(hs_service_t *service)
+{
+ if (service == NULL) {
+ return;
+ }
+
+ /* Free descriptors. Go over both descriptor with this loop. */
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ service_descriptor_free(desc);
+ } FOR_EACH_DESCRIPTOR_END;
+
+ /* Free service configuration. */
+ service_clear_config(&service->config);
+
+ /* Free replay cache from state. */
+ if (service->state.replay_cache_rend_cookie) {
+ replaycache_free(service->state.replay_cache_rend_cookie);
+ }
+
+ /* Wipe service keys. */
+ memwipe(&service->keys.identity_sk, 0, sizeof(service->keys.identity_sk));
+
+ tor_free(service);
+}
+
+/* Periodic callback. Entry point from the main loop to the HS service
+ * subsystem. This is call every second. This is skipped if tor can't build a
+ * circuit or the network is disabled. */
+void
+hs_service_run_scheduled_events(time_t now)
+{
+ /* First thing we'll do here is to make sure our services are in a
+ * quiescent state for the scheduled events. */
+ run_housekeeping_event(now);
+
+ /* Order matters here. We first make sure the descriptor object for each
+ * service contains the latest data. Once done, we check if we need to open
+ * new introduction circuit. Finally, we try to upload the descriptor for
+ * each service. */
+
+ /* Make sure descriptors are up to date. */
+ run_build_descriptor_event(now);
+ /* Make sure services have enough circuits. */
+ run_build_circuit_event(now);
+ /* Upload the descriptors if needed/possible. */
+ run_upload_descriptor_event(now);
+}
+
+/* Initialize the service HS subsystem. */
+void
+hs_service_init(void)
+{
+ /* Should never be called twice. */
+ tor_assert(!hs_service_map);
+ tor_assert(!hs_service_staging_list);
+
+ /* v2 specific. */
+ rend_service_init();
+
+ hs_service_map = tor_malloc_zero(sizeof(struct hs_service_ht));
+ HT_INIT(hs_service_ht, hs_service_map);
+
+ hs_service_staging_list = smartlist_new();
+}
+
+/* Release all global storage of the hidden service subsystem. */
+void
+hs_service_free_all(void)
+{
+ rend_service_free_all();
+ service_free_all();
+}
+
+#ifdef TOR_UNIT_TESTS
+
+/* Return the global service map size. Only used by unit test. */
+STATIC unsigned int
+get_hs_service_map_size(void)
+{
+ return HT_SIZE(hs_service_map);
+}
+
+/* Return the staging list size. Only used by unit test. */
+STATIC int
+get_hs_service_staging_list_size(void)
+{
+ return smartlist_len(hs_service_staging_list);
+}
+
+STATIC hs_service_ht *
+get_hs_service_map(void)
+{
+ return hs_service_map;
+}
+
+STATIC hs_service_t *
+get_first_service(void)
+{
+ hs_service_t **obj = HT_START(hs_service_ht, hs_service_map);
+ if (obj == NULL) {
+ return NULL;
+ }
+ return *obj;
+}
+
+#endif /* TOR_UNIT_TESTS */
+
diff --git a/src/or/hs_service.h b/src/or/hs_service.h
index 3302592762..57717fc927 100644
--- a/src/or/hs_service.h
+++ b/src/or/hs_service.h
@@ -3,25 +3,362 @@
/**
* \file hs_service.h
- * \brief Header file for hs_service.c.
+ * \brief Header file containing service data for the HS subsytem.
**/
#ifndef TOR_HS_SERVICE_H
#define TOR_HS_SERVICE_H
-#include "or.h"
+#include "crypto_curve25519.h"
+#include "crypto_ed25519.h"
+#include "replaycache.h"
+
+#include "hs_common.h"
+#include "hs_descriptor.h"
+#include "hs_ident.h"
+#include "hs_intropoint.h"
+
+/* Trunnel */
#include "hs/cell_establish_intro.h"
-/* These functions are only used by unit tests and we need to expose them else
- * hs_service.o ends up with no symbols in libor.a which makes clang throw a
- * warning at compile time. See #21825. */
+/* When loading and configuring a service, this is the default version it will
+ * be configured for as it is possible that no HiddenServiceVersion is
+ * present. */
+#define HS_SERVICE_DEFAULT_VERSION HS_VERSION_TWO
+
+/* As described in the specification, service publishes their next descriptor
+ * at a random time between those two values (in seconds). */
+#define HS_SERVICE_NEXT_UPLOAD_TIME_MIN (60 * 60)
+#define HS_SERVICE_NEXT_UPLOAD_TIME_MAX (120 * 60)
+
+/* Service side introduction point. */
+typedef struct hs_service_intro_point_t {
+ /* Top level intropoint "shared" data between client/service. */
+ hs_intropoint_t base;
+
+ /* Onion key of the introduction point used to extend to it for the ntor
+ * handshake. */
+ curve25519_public_key_t onion_key;
+
+ /* Authentication keypair used to create the authentication certificate
+ * which is published in the descriptor. */
+ ed25519_keypair_t auth_key_kp;
+
+ /* Encryption keypair for the "ntor" type. */
+ curve25519_keypair_t enc_key_kp;
+
+ /* Legacy key if that intro point doesn't support v3. This should be used if
+ * the base object legacy flag is set. */
+ crypto_pk_t *legacy_key;
+
+ /* Amount of INTRODUCE2 cell accepted from this intro point. */
+ uint64_t introduce2_count;
+
+ /* Maximum number of INTRODUCE2 cell this intro point should accept. */
+ uint64_t introduce2_max;
+
+ /* The time at which this intro point should expire and stop being used. */
+ time_t time_to_expire;
+
+ /* The amount of circuit creation we've made to this intro point. This is
+ * incremented every time we do a circuit relaunch on this intro point which
+ * is triggered when the circuit dies but the node is still in the
+ * consensus. After MAX_INTRO_POINT_CIRCUIT_RETRIES, we give up on it. */
+ uint32_t circuit_retries;
+
+ /* Set if this intro point has an established circuit. */
+ unsigned int circuit_established : 1;
+
+ /* Replay cache recording the encrypted part of an INTRODUCE2 cell that the
+ * circuit associated with this intro point has received. This is used to
+ * prevent replay attacks. */
+ replaycache_t *replay_cache;
+} hs_service_intro_point_t;
+
+/* Object handling introduction points of a service. */
+typedef struct hs_service_intropoints_t {
+ /* The time at which we've started our retry period to build circuits. We
+ * don't want to stress circuit creation so we can only retry for a certain
+ * time and then after we stop and wait. */
+ time_t retry_period_started;
+
+ /* Number of circuit we've launched during a single retry period. */
+ unsigned int num_circuits_launched;
+
+ /* Contains the current hs_service_intro_point_t objects indexed by
+ * authentication public key. */
+ digest256map_t *map;
+
+ /* Contains node's identity key digest that were introduction point for this
+ * descriptor but were retried to many times. We keep those so we avoid
+ * re-picking them over and over for a circuit retry period.
+ * XXX: Once we have #22173, change this to only use ed25519 identity. */
+ digestmap_t *failed_id;
+} hs_service_intropoints_t;
+
+/* Representation of a service descriptor. */
+typedef struct hs_service_descriptor_t {
+ /* Decoded descriptor. This object is used for encoding when the service
+ * publishes the descriptor. */
+ hs_descriptor_t *desc;
+
+ /* Descriptor signing keypair. */
+ ed25519_keypair_t signing_kp;
+
+ /* Blinded keypair derived from the master identity public key. */
+ ed25519_keypair_t blinded_kp;
+
+ /* When is the next time when we should upload the descriptor. */
+ time_t next_upload_time;
+
+ /* Introduction points assign to this descriptor which contains
+ * hs_service_intropoints_t object indexed by authentication key (the RSA
+ * key if the node is legacy). */
+ hs_service_intropoints_t intro_points;
+
+ /* The time period number this descriptor has been created for. */
+ uint64_t time_period_num;
+
+ /* True iff we have missing intro points for this descriptor because we
+ * couldn't pick any nodes. */
+ unsigned int missing_intro_points : 1;
+
+ /* List of identity digests for hidden service directories to which we
+ * couldn't upload this descriptor because we didn't have its router
+ * descriptor at the time. If this list is non-empty, only the relays in this
+ * list are re-tried to upload this descriptor when our directory information
+ * have been updated. */
+ smartlist_t *hsdir_missing_info;
+
+ /** List of the responsible HSDirs (their b64ed identity digest) last time we
+ * uploaded this descriptor. If the set of responsible HSDirs is different
+ * from this list, this means we received new dirinfo and we need to
+ * reupload our descriptor. This list is always sorted lexicographically. */
+ smartlist_t *previous_hsdirs;
+} hs_service_descriptor_t;
+
+/* Service key material. */
+typedef struct hs_service_keys_t {
+ /* Master identify public key. */
+ ed25519_public_key_t identity_pk;
+ /* Master identity private key. */
+ ed25519_secret_key_t identity_sk;
+ /* True iff the key is kept offline which means the identity_sk MUST not be
+ * used in that case. */
+ unsigned int is_identify_key_offline : 1;
+} hs_service_keys_t;
+
+/* Service configuration. The following are set from the torrc options either
+ * set by the configuration file or by the control port. Nothing else should
+ * change those values. */
+typedef struct hs_service_config_t {
+ /* Protocol version of the service. Specified by HiddenServiceVersion
+ * option. */
+ uint32_t version;
+
+ /* List of rend_service_port_config_t */
+ smartlist_t *ports;
+
+ /* Path on the filesystem where the service persistent data is stored. NULL
+ * if the service is ephemeral. Specified by HiddenServiceDir option. */
+ char *directory_path;
+
+ /* The maximum number of simultaneous streams per rendezvous circuit that
+ * are allowed to be created. No limit if 0. Specified by
+ * HiddenServiceMaxStreams option. */
+ uint64_t max_streams_per_rdv_circuit;
+
+ /* If true, we close circuits that exceed the max_streams_per_rdv_circuit
+ * limit. Specified by HiddenServiceMaxStreamsCloseCircuit option. */
+ unsigned int max_streams_close_circuit : 1;
+
+ /* How many introduction points this service has. Specified by
+ * HiddenServiceNumIntroductionPoints option. */
+ unsigned int num_intro_points;
+
+ /* True iff we allow request made on unknown ports. Specified by
+ * HiddenServiceAllowUnknownPorts option. */
+ unsigned int allow_unknown_ports : 1;
+
+ /* If true, this service is a Single Onion Service. Specified by
+ * HiddenServiceSingleHopMode and HiddenServiceNonAnonymousMode options. */
+ unsigned int is_single_onion : 1;
+
+ /* If true, allow group read permissions on the directory_path. Specified by
+ * HiddenServiceDirGroupReadable option. */
+ unsigned int dir_group_readable : 1;
+
+ /* Is this service ephemeral? */
+ unsigned int is_ephemeral : 1;
+} hs_service_config_t;
+
+/* Service state. */
+typedef struct hs_service_state_t {
+ /* The time at which we've started our retry period to build circuits. We
+ * don't want to stress circuit creation so we can only retry for a certain
+ * time and then after we stop and wait. */
+ time_t intro_circ_retry_started_time;
+
+ /* Number of circuit we've launched during a single retry period. This
+ * should never go over MAX_INTRO_CIRCS_PER_PERIOD. */
+ unsigned int num_intro_circ_launched;
+
+ /* Indicate that the service has entered the overlap period. We use this
+ * flag to check for descriptor rotation. */
+ unsigned int in_overlap_period : 1;
+
+ /* Replay cache tracking the REND_COOKIE found in INTRODUCE2 cell to detect
+ * repeats. Clients may send INTRODUCE1 cells for the same rendezvous point
+ * through two or more different introduction points; when they do, this
+ * keeps us from launching multiple simultaneous attempts to connect to the
+ * same rend point. */
+ replaycache_t *replay_cache_rend_cookie;
+} hs_service_state_t;
+
+/* Representation of a service running on this tor instance. */
+typedef struct hs_service_t {
+ /* Onion address base32 encoded and NUL terminated. We keep it for logging
+ * purposes so we don't have to build it everytime. */
+ char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1];
+
+ /* Hashtable node: use to look up the service by its master public identity
+ * key in the service global map. */
+ HT_ENTRY(hs_service_t) hs_service_node;
+
+ /* Service state which contains various flags and counters. */
+ hs_service_state_t state;
+
+ /* Key material of the service. */
+ hs_service_keys_t keys;
+
+ /* Configuration of the service. */
+ hs_service_config_t config;
+
+ /* Current descriptor. */
+ hs_service_descriptor_t *desc_current;
+ /* Next descriptor that we need for the overlap period for which we have to
+ * keep two sets of opened introduction point circuits. */
+ hs_service_descriptor_t *desc_next;
+
+ /* XXX: Credential (client auth.) #20700. */
+
+} hs_service_t;
+
+/* For the service global hash map, we define a specific type for it which
+ * will make it safe to use and specific to some controlled parameters such as
+ * the hashing function and how to compare services. */
+typedef HT_HEAD(hs_service_ht, hs_service_t) hs_service_ht;
+
+/* API */
+
+/* Global initializer and cleanup function. */
+void hs_service_init(void);
+void hs_service_free_all(void);
+
+/* Service new/free functions. */
+hs_service_t *hs_service_new(const or_options_t *options);
+void hs_service_free(hs_service_t *service);
+
+unsigned int hs_service_get_num_services(void);
+void hs_service_stage_services(const smartlist_t *service_list);
+int hs_service_load_all_keys(void);
+void hs_service_lists_fnames_for_sandbox(smartlist_t *file_list,
+ smartlist_t *dir_list);
+int hs_service_set_conn_addr_port(const origin_circuit_t *circ,
+ edge_connection_t *conn);
+void hs_hsdir_set_changed_consider_reupload(void);
+
+void hs_service_dir_info_changed(void);
+void hs_service_run_scheduled_events(time_t now);
+void hs_service_circuit_has_opened(origin_circuit_t *circ);
+int hs_service_receive_intro_established(origin_circuit_t *circ,
+ const uint8_t *payload,
+ size_t payload_len);
+int hs_service_receive_introduce2(origin_circuit_t *circ,
+ const uint8_t *payload,
+ size_t payload_len);
+
+void hs_service_intro_circ_has_closed(origin_circuit_t *circ);
+
+#ifdef HS_SERVICE_PRIVATE
+
+#ifdef TOR_UNIT_TESTS
+
+/* Useful getters for unit tests. */
+STATIC unsigned int get_hs_service_map_size(void);
+STATIC int get_hs_service_staging_list_size(void);
+STATIC hs_service_ht *get_hs_service_map(void);
+STATIC hs_service_t *get_first_service(void);
+
+/* Service accessors. */
+STATIC hs_service_t *find_service(hs_service_ht *map,
+ const ed25519_public_key_t *pk);
+STATIC void remove_service(hs_service_ht *map, hs_service_t *service);
+STATIC int register_service(hs_service_ht *map, hs_service_t *service);
+/* Service introduction point functions. */
+STATIC hs_service_intro_point_t *service_intro_point_new(
+ const extend_info_t *ei,
+ unsigned int is_legacy);
+STATIC void service_intro_point_free(hs_service_intro_point_t *ip);
+STATIC void service_intro_point_add(digest256map_t *map,
+ hs_service_intro_point_t *ip);
+STATIC void service_intro_point_remove(const hs_service_t *service,
+ const hs_service_intro_point_t *ip);
+STATIC hs_service_intro_point_t *service_intro_point_find(
+ const hs_service_t *service,
+ const ed25519_public_key_t *auth_key);
+STATIC hs_service_intro_point_t *service_intro_point_find_by_ident(
+ const hs_service_t *service,
+ const hs_ident_circuit_t *ident);
+/* Service descriptor functions. */
+STATIC hs_service_descriptor_t *service_descriptor_new(void);
+STATIC hs_service_descriptor_t *service_desc_find_by_intro(
+ const hs_service_t *service,
+ const hs_service_intro_point_t *ip);
+/* Helper functions. */
+STATIC void get_objects_from_ident(const hs_ident_circuit_t *ident,
+ hs_service_t **service,
+ hs_service_intro_point_t **ip,
+ hs_service_descriptor_t **desc);
+STATIC const node_t *
+get_node_from_intro_point(const hs_service_intro_point_t *ip);
+STATIC int can_service_launch_intro_circuit(hs_service_t *service,
+ time_t now);
+STATIC int intro_point_should_expire(const hs_service_intro_point_t *ip,
+ time_t now);
+STATIC void run_housekeeping_event(time_t now);
+STATIC void rotate_all_descriptors(time_t now);
+STATIC void build_all_descriptors(time_t now);
+STATIC void update_all_descriptors(time_t now);
+STATIC void run_upload_descriptor_event(time_t now);
+
+STATIC char *
+encode_desc_rev_counter_for_state(const hs_service_descriptor_t *desc);
+
+STATIC void service_descriptor_free(hs_service_descriptor_t *desc);
+
+STATIC uint64_t
+check_state_line_for_service_rev_counter(const char *state_line,
+ const ed25519_public_key_t *blinded_pubkey,
+ int *service_found_out);
+
+STATIC int
+write_address_to_file(const hs_service_t *service, const char *fname_);
+
+STATIC void upload_descriptor_to_all(const hs_service_t *service,
+ hs_service_descriptor_t *desc,
+ int for_next_period);
+
+STATIC void service_desc_schedule_upload(hs_service_descriptor_t *desc,
+ time_t now,
+ int descriptor_changed);
+
+STATIC int service_desc_hsdirs_changed(const hs_service_t *service,
+ const hs_service_descriptor_t *desc);
+
+#endif /* TOR_UNIT_TESTS */
-trn_cell_establish_intro_t *
-generate_establish_intro_cell(const uint8_t *circuit_key_material,
- size_t circuit_key_material_len);
-ssize_t
-get_establish_intro_payload(uint8_t *buf, size_t buf_len,
- const trn_cell_establish_intro_t *cell);
+#endif /* HS_SERVICE_PRIVATE */
#endif /* TOR_HS_SERVICE_H */
diff --git a/src/or/include.am b/src/or/include.am
index 1ef5afa013..69b505fcd4 100644
--- a/src/or/include.am
+++ b/src/or/include.am
@@ -50,16 +50,21 @@ LIBTOR_A_SOURCES = \
src/or/dnsserv.c \
src/or/fp_pair.c \
src/or/geoip.c \
- src/or/hs_intropoint.c \
- src/or/hs_circuitmap.c \
- src/or/hs_ntor.c \
- src/or/hs_service.c \
src/or/entrynodes.c \
src/or/ext_orport.c \
src/or/hibernate.c \
src/or/hs_cache.c \
+ src/or/hs_cell.c \
+ src/or/hs_circuit.c \
+ src/or/hs_circuitmap.c \
+ src/or/hs_client.c \
src/or/hs_common.c \
+ src/or/hs_config.c \
src/or/hs_descriptor.c \
+ src/or/hs_ident.c \
+ src/or/hs_intropoint.c \
+ src/or/hs_ntor.c \
+ src/or/hs_service.c \
src/or/keypin.c \
src/or/main.c \
src/or/microdesc.c \
@@ -122,10 +127,11 @@ src_or_tor_LDADD = src/or/libtor.a src/common/libor.a src/common/libor-ctime.a \
src/common/libor-crypto.a $(LIBKECCAK_TINY) $(LIBDONNA) \
src/common/libor-event.a src/trunnel/libor-trunnel.a \
src/trace/libor-trace.a \
+ $(rust_ldadd) \
@TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ \
- @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ @TOR_SYSTEMD_LIBS@ \
- @TOR_LZMA_LIBS@ @TOR_ZSTD_LIBS@ \
- $(rust_ldadd)
+ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @TOR_LIB_USERENV@ \
+ @CURVE25519_LIBS@ @TOR_SYSTEMD_LIBS@ \
+ @TOR_LZMA_LIBS@ @TOR_ZSTD_LIBS@
if COVERAGE_ENABLED
src_or_tor_cov_SOURCES = src/or/tor_main.c
@@ -180,12 +186,17 @@ ORHEADERS = \
src/or/entrynodes.h \
src/or/hibernate.h \
src/or/hs_cache.h \
+ src/or/hs_cell.h \
+ src/or/hs_config.h \
+ src/or/hs_circuit.h \
+ src/or/hs_circuitmap.h \
+ src/or/hs_client.h \
src/or/hs_common.h \
src/or/hs_descriptor.h \
- src/or/hs_intropoint.h \
- src/or/hs_circuitmap.h \
- src/or/hs_ntor.h \
- src/or/hs_service.h \
+ src/or/hs_ident.h \
+ src/or/hs_intropoint.h \
+ src/or/hs_ntor.h \
+ src/or/hs_service.h \
src/or/keypin.h \
src/or/main.h \
src/or/microdesc.h \
diff --git a/src/or/main.c b/src/or/main.c
index cb24fd18c8..5d51d1dead 100644
--- a/src/or/main.c
+++ b/src/or/main.c
@@ -1194,6 +1194,7 @@ CALLBACK(heartbeat);
CALLBACK(clean_consdiffmgr);
CALLBACK(reset_padding_counts);
CALLBACK(check_canonical_channels);
+CALLBACK(hs_service);
#undef CALLBACK
@@ -1229,6 +1230,7 @@ static periodic_event_item_t periodic_events[] = {
CALLBACK(clean_consdiffmgr),
CALLBACK(reset_padding_counts),
CALLBACK(check_canonical_channels),
+ CALLBACK(hs_service),
END_OF_PERIODIC_EVENTS
};
#undef CALLBACK
@@ -1461,12 +1463,6 @@ run_scheduled_events(time_t now)
/* 6. And remove any marked circuits... */
circuit_close_all_marked();
- /* 7. And upload service descriptors if necessary. */
- if (have_completed_a_circuit() && !net_is_disabled()) {
- rend_consider_services_upload(now);
- rend_consider_descriptor_republication();
- }
-
/* 8. and blow away any connections that need to die. have to do this now,
* because if we marked a conn for close and left its socket -1, then
* we'll pass it to poll/select and bad things will happen.
@@ -1713,7 +1709,7 @@ check_expired_networkstatus_callback(time_t now, const or_options_t *options)
* networkstatus_get_reasonably_live_consensus(), but that value is way
* way too high. Arma: is the bridge issue there resolved yet? -NM */
#define NS_EXPIRY_SLOP (24*60*60)
- if (ns && ns->valid_until < now+NS_EXPIRY_SLOP &&
+ if (ns && ns->valid_until < (now - NS_EXPIRY_SLOP) &&
router_have_minimum_dir_info()) {
router_dir_info_changed();
}
@@ -1831,8 +1827,8 @@ clean_caches_callback(time_t now, const or_options_t *options)
{
/* Remove old information from rephist and the rend cache. */
rep_history_clean(now - options->RephistTrackTime);
- rend_cache_clean(now, REND_CACHE_TYPE_CLIENT);
rend_cache_clean(now, REND_CACHE_TYPE_SERVICE);
+ hs_cache_clean_as_client(now);
hs_cache_clean_as_dir(now);
microdesc_cache_rebuild(NULL, 0);
#define CLEAN_CACHES_INTERVAL (30*60)
@@ -1851,6 +1847,7 @@ rend_cache_failure_clean_callback(time_t now, const or_options_t *options)
* clean it as soon as we can since we want to make sure the client waits
* as little as possible for reachability reasons. */
rend_cache_failure_clean(now);
+ hs_cache_client_intro_state_clean(now);
return 30;
}
@@ -2040,7 +2037,8 @@ check_fw_helper_app_callback(time_t now, const or_options_t *options)
{
if (net_is_disabled() ||
! server_mode(options) ||
- ! options->PortForwarding) {
+ ! options->PortForwarding ||
+ options->NoExec) {
return PERIODIC_EVENT_NO_UPDATE;
}
/* 11. check the port forwarding app */
@@ -2060,6 +2058,9 @@ check_fw_helper_app_callback(time_t now, const or_options_t *options)
/**
* Periodic callback: write the heartbeat message in the logs.
+ *
+ * If writing the heartbeat message to the logs fails for some reason, retry
+ * again after <b>MIN_HEARTBEAT_PERIOD</b> seconds.
*/
static int
heartbeat_callback(time_t now, const or_options_t *options)
@@ -2071,14 +2072,20 @@ heartbeat_callback(time_t now, const or_options_t *options)
return PERIODIC_EVENT_NO_UPDATE;
}
- /* Write the heartbeat message */
+ /* Skip the first one. */
if (first) {
- first = 0; /* Skip the first one. */
- } else {
- log_heartbeat(now);
+ first = 0;
+ return options->HeartbeatPeriod;
}
- return options->HeartbeatPeriod;
+ /* Write the heartbeat message */
+ if (log_heartbeat(now) == 0) {
+ return options->HeartbeatPeriod;
+ } else {
+ /* If we couldn't write the heartbeat log message, try again in the minimum
+ * interval of time. */
+ return MIN_HEARTBEAT_PERIOD;
+ }
}
#define CDM_CLEAN_CALLBACK_INTERVAL 600
@@ -2092,6 +2099,28 @@ clean_consdiffmgr_callback(time_t now, const or_options_t *options)
return CDM_CLEAN_CALLBACK_INTERVAL;
}
+/*
+ * Periodic callback: Run scheduled events for HS service. This is called
+ * every second.
+ */
+static int
+hs_service_callback(time_t now, const or_options_t *options)
+{
+ (void) options;
+
+ /* We need to at least be able to build circuits and that we actually have
+ * a working network. */
+ if (!have_completed_a_circuit() || net_is_disabled()) {
+ goto end;
+ }
+
+ hs_service_run_scheduled_events(now);
+
+ end:
+ /* Every 1 second. */
+ return 1;
+}
+
/** Timer: used to invoke second_elapsed_callback() once per second. */
static periodic_timer_t *second_timer = NULL;
/** Number of libevent errors in the last second: we die if we get too many. */
@@ -2355,7 +2384,7 @@ do_hup(void)
tor_free(msg);
}
}
- if (authdir_mode_handles_descs(options, -1)) {
+ if (authdir_mode(options)) {
/* reload the approved-routers file */
if (dirserv_load_fingerprint_file() < 0) {
/* warnings are logged from dirserv_load_fingerprint_file() directly */
@@ -2499,9 +2528,6 @@ do_main_loop(void)
}
}
- /* Initialize relay-side HS circuitmap */
- hs_circuitmap_init();
-
/* set up once-a-second callback. */
if (! second_timer) {
struct timeval one_second;
@@ -2877,7 +2903,6 @@ dumpstats(int severity)
rep_hist_dump_stats(now,severity);
rend_service_dump_stats(severity);
- dump_pk_ops(severity);
dump_distinct_digest_count(severity);
}
@@ -3014,9 +3039,10 @@ tor_init(int argc, char *argv[])
rep_hist_init();
/* Initialize the service cache. */
rend_cache_init();
- hs_cache_init();
addressmap_init(); /* Init the client dns cache. Do it always, since it's
* cheap. */
+ /* Initialize the HS subsystem. */
+ hs_init();
{
/* We search for the "quiet" option first, since it decides whether we
@@ -3216,10 +3242,8 @@ tor_free_all(int postfork)
networkstatus_free_all();
addressmap_free_all();
dirserv_free_all();
- rend_service_free_all();
rend_cache_free_all();
rend_service_authorization_free_all();
- hs_cache_free_all();
rep_hist_free_all();
dns_free_all();
clear_pending_onions();
@@ -3232,7 +3256,6 @@ tor_free_all(int postfork)
connection_edge_free_all();
scheduler_free_all();
nodelist_free_all();
- hs_circuitmap_free_all();
microdesc_free_all();
routerparse_free_all();
ext_orport_free_all();
@@ -3241,6 +3264,7 @@ tor_free_all(int postfork)
protover_free_all();
bridges_free_all();
consdiffmgr_free_all();
+ hs_free_all();
if (!postfork) {
config_free_all();
or_state_free_all();
@@ -3478,7 +3502,7 @@ sandbox_init_filter(void)
if (options->BridgeAuthoritativeDir)
OPEN_DATADIR_SUFFIX("networkstatus-bridges", ".tmp");
- if (authdir_mode_handles_descs(options, -1))
+ if (authdir_mode(options))
OPEN_DATADIR("approved-routers");
if (options->ServerDNSResolvConfFile)
@@ -3550,7 +3574,7 @@ sandbox_init_filter(void)
{
smartlist_t *files = smartlist_new();
smartlist_t *dirs = smartlist_new();
- rend_services_add_filenames_to_lists(files, dirs);
+ hs_service_lists_fnames_for_sandbox(files, dirs);
SMARTLIST_FOREACH(files, char *, file_name, {
char *tmp_name = NULL;
tor_asprintf(&tmp_name, "%s.tmp", file_name);
@@ -3754,6 +3778,11 @@ tor_main(int argc, char *argv[])
case CMD_KEYGEN:
result = load_ed_keys(get_options(), time(NULL)) < 0;
break;
+ case CMD_KEY_EXPIRATION:
+ init_keys();
+ result = log_cert_expiration();
+ result = 0;
+ break;
case CMD_LIST_FINGERPRINT:
result = do_list_fingerprint();
break;
diff --git a/src/or/microdesc.c b/src/or/microdesc.c
index a4e6b409c4..18a6fbded7 100644
--- a/src/or/microdesc.c
+++ b/src/or/microdesc.c
@@ -876,7 +876,7 @@ update_microdesc_downloads(time_t now)
smartlist_free(missing);
}
-/** For every microdescriptor listed in the current microdecriptor consensus,
+/** For every microdescriptor listed in the current microdescriptor consensus,
* update its last_listed field to be at least as recent as the publication
* time of the current microdescriptor consensus.
*/
diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c
index 25d79139b9..69bff55cff 100644
--- a/src/or/networkstatus.c
+++ b/src/or/networkstatus.c
@@ -76,7 +76,7 @@ static strmap_t *unnamed_server_map = NULL;
* status. */
STATIC networkstatus_t *current_ns_consensus = NULL;
-/** Most recently received and validated v3 "microdec"-flavored consensus
+/** Most recently received and validated v3 "microdesc"-flavored consensus
* network status. */
STATIC networkstatus_t *current_md_consensus = NULL;
@@ -1393,14 +1393,21 @@ networkstatus_get_latest_consensus_by_flavor,(consensus_flavor_t f))
MOCK_IMPL(networkstatus_t *,
networkstatus_get_live_consensus,(time_t now))
{
- if (networkstatus_get_latest_consensus() &&
- networkstatus_get_latest_consensus()->valid_after <= now &&
- now <= networkstatus_get_latest_consensus()->valid_until)
- return networkstatus_get_latest_consensus();
+ networkstatus_t *ns = networkstatus_get_latest_consensus();
+ if (ns && networkstatus_is_live(ns, now))
+ return ns;
else
return NULL;
}
+/** Given a consensus in <b>ns</b>, return true iff currently live and
+ * unexpired. */
+int
+networkstatus_is_live(const networkstatus_t *ns, time_t now)
+{
+ return (ns->valid_after <= now && now <= ns->valid_until);
+}
+
/** Determine if <b>consensus</b> is valid or expired recently enough that
* we can still use it.
*
@@ -1783,7 +1790,7 @@ networkstatus_set_current_consensus(const char *consensus,
if (from_cache && !was_waiting_for_certs) {
/* We previously stored this; check _now_ to make sure that version-kills
- * really work. This happens even before we check signatures: we did so
+ * really work. This happens even before we check signatures: we did so
* before when we stored this to disk. This does mean an attacker who can
* write to the datadir can make us not start: such an attacker could
* already harm us by replacing our guards, which would be worse. */
diff --git a/src/or/networkstatus.h b/src/or/networkstatus.h
index e774c4d266..f9320747d2 100644
--- a/src/or/networkstatus.h
+++ b/src/or/networkstatus.h
@@ -81,6 +81,7 @@ MOCK_DECL(networkstatus_t *,networkstatus_get_latest_consensus,(void));
MOCK_DECL(networkstatus_t *,networkstatus_get_latest_consensus_by_flavor,
(consensus_flavor_t f));
MOCK_DECL(networkstatus_t *, networkstatus_get_live_consensus,(time_t now));
+int networkstatus_is_live(const networkstatus_t *ns, time_t now);
int networkstatus_consensus_reasonably_live(const networkstatus_t *consensus,
time_t now);
int networkstatus_valid_until_is_reasonably_live(time_t valid_until,
diff --git a/src/or/nodelist.c b/src/or/nodelist.c
index 3ac5c3e302..6acc87f967 100644
--- a/src/or/nodelist.c
+++ b/src/or/nodelist.c
@@ -45,6 +45,7 @@
#include "dirserv.h"
#include "entrynodes.h"
#include "geoip.h"
+#include "hs_common.h"
#include "main.h"
#include "microdesc.h"
#include "networkstatus.h"
@@ -54,6 +55,7 @@
#include "rendservice.h"
#include "router.h"
#include "routerlist.h"
+#include "routerparse.h"
#include "routerset.h"
#include "torcert.h"
@@ -164,12 +166,99 @@ node_get_or_create(const char *identity_digest)
smartlist_add(the_nodelist->nodes, node);
node->nodelist_idx = smartlist_len(the_nodelist->nodes) - 1;
+ node->hsdir_index = tor_malloc_zero(sizeof(hsdir_index_t));
node->country = -1;
return node;
}
+/* For a given <b>node</b> for the consensus <b>ns</b>, set the hsdir index
+ * for the node, both current and next if possible. This can only fails if the
+ * node_t ed25519 identity key can't be found which would be a bug. */
+static void
+node_set_hsdir_index(node_t *node, const networkstatus_t *ns)
+{
+ time_t now = approx_time();
+ const ed25519_public_key_t *node_identity_pk;
+ uint8_t *next_hsdir_index_srv = NULL, *current_hsdir_index_srv = NULL;
+ uint64_t next_time_period_num, current_time_period_num;
+
+ tor_assert(node);
+ tor_assert(ns);
+
+ if (!networkstatus_is_live(ns, now)) {
+ log_info(LD_GENERAL, "Not setting hsdir index with a non-live consensus.");
+ goto done;
+ }
+
+ node_identity_pk = node_get_ed25519_id(node);
+ if (node_identity_pk == NULL) {
+ log_debug(LD_GENERAL, "ed25519 identity public key not found when "
+ "trying to build the hsdir indexes for node %s",
+ node_describe(node));
+ goto done;
+ }
+
+ /* Get the current and next time period number, we might use them both. */
+ current_time_period_num = hs_get_time_period_num(now);
+ next_time_period_num = hs_get_next_time_period_num(now);
+
+ if (hs_overlap_mode_is_active(ns, now)) {
+ /* We are in overlap mode, this means that our consensus has just cycled
+ * from current SRV to previous SRV so for the _next_ upcoming time
+ * period, we have to use the current SRV and use the previous SRV for the
+ * current time period. If the current or previous SRV can't be found, the
+ * disaster one is returned. */
+ next_hsdir_index_srv = hs_get_current_srv(next_time_period_num, ns);
+ /* The following can be confusing so again, in overlap mode, we use our
+ * previous SRV for our _current_ hsdir index. */
+ current_hsdir_index_srv = hs_get_previous_srv(current_time_period_num, ns);
+ } else {
+ /* If NOT in overlap mode, we only need to compute the current hsdir index
+ * for the ongoing time period and thus the current SRV. If it can't be
+ * found, the disaster one is returned. */
+ current_hsdir_index_srv = hs_get_current_srv(current_time_period_num, ns);
+ }
+
+ /* Build the current hsdir index. */
+ hs_build_hsdir_index(node_identity_pk, current_hsdir_index_srv,
+ current_time_period_num, node->hsdir_index->current);
+ if (next_hsdir_index_srv) {
+ /* Build the next hsdir index if we have a next SRV that we can use. */
+ hs_build_hsdir_index(node_identity_pk, next_hsdir_index_srv,
+ next_time_period_num, node->hsdir_index->next);
+ } else {
+ memset(node->hsdir_index->next, 0, sizeof(node->hsdir_index->next));
+ }
+
+ done:
+ tor_free(current_hsdir_index_srv);
+ tor_free(next_hsdir_index_srv);
+ return;
+}
+
+/** Recompute all node hsdir indices. */
+void
+nodelist_recompute_all_hsdir_indices(void)
+{
+ networkstatus_t *consensus;
+ if (!the_nodelist) {
+ return;
+ }
+
+ /* Get a live consensus. Abort if not found */
+ consensus = networkstatus_get_live_consensus(approx_time());
+ if (!consensus) {
+ return;
+ }
+
+ /* Recompute all hsdir indices */
+ SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) {
+ node_set_hsdir_index(node, consensus);
+ } SMARTLIST_FOREACH_END(node);
+}
+
/** Called when a node's address changes. */
static void
node_addrs_changed(node_t *node)
@@ -216,6 +305,14 @@ nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out)
dirserv_set_node_flags_from_authoritative_status(node, status);
}
+ /* Setting the HSDir index requires the ed25519 identity key which can
+ * only be found either in the ri or md. This is why this is called here.
+ * Only nodes supporting HSDir=2 protocol version needs this index. */
+ if (node->rs && node->rs->supports_v3_hsdir) {
+ node_set_hsdir_index(node,
+ networkstatus_get_latest_consensus());
+ }
+
return node;
}
@@ -246,6 +343,12 @@ nodelist_add_microdesc(microdesc_t *md)
node->md->held_by_nodes--;
node->md = md;
md->held_by_nodes++;
+ /* Setting the HSDir index requires the ed25519 identity key which can
+ * only be found either in the ri or md. This is why this is called here.
+ * Only nodes supporting HSDir=2 protocol version needs this index. */
+ if (rs->supports_v3_hsdir) {
+ node_set_hsdir_index(node, ns);
+ }
}
return node;
}
@@ -283,6 +386,9 @@ nodelist_set_consensus(networkstatus_t *ns)
}
}
+ if (rs->supports_v3_hsdir) {
+ node_set_hsdir_index(node, ns);
+ }
node_set_country(node);
/* If we're not an authdir, believe others. */
@@ -410,6 +516,7 @@ node_free(node_t *node)
if (node->md)
node->md->held_by_nodes--;
tor_assert(node->nodelist_idx == -1);
+ tor_free(node->hsdir_index);
tor_free(node);
}
@@ -630,11 +737,9 @@ node_get_by_nickname,(const char *nickname, int warn_if_unnamed))
if (! node->name_lookup_warned) {
base16_encode(fp, sizeof(fp), node->identity, DIGEST_LEN);
log_warn(LD_CONFIG,
- "You specified a server \"%s\" by name, but the directory "
- "authorities do not have any key registered for this "
- "nickname -- so it could be used by any server, not just "
- "the one you meant. "
- "To make sure you get the same server in the future, refer "
+ "You specified a relay \"%s\" by name, but nicknames can be "
+ "used by any relay, not just the one you meant. "
+ "To make sure you get the same relay in the future, refer "
"to it by key, as \"$%s\".", nickname, fp);
node->name_lookup_warned = 1;
}
@@ -707,6 +812,57 @@ node_supports_ed25519_link_authentication(const node_t *node)
return 0;
}
+/** Return true iff <b>node</b> supports the hidden service directory version
+ * 3 protocol (proposal 224). */
+int
+node_supports_v3_hsdir(const node_t *node)
+{
+ tor_assert(node);
+
+ if (node->rs) {
+ return node->rs->supports_v3_hsdir;
+ }
+ if (node->ri) {
+ if (node->ri->protocol_list == NULL) {
+ return 0;
+ }
+ /* Bug #22447 forces us to filter on tor version:
+ * If platform is a Tor version, and older than 0.3.0.8, return False.
+ * Else, obey the protocol list. */
+ if (node->ri->platform) {
+ if (!strcmpstart(node->ri->platform, "Tor ") &&
+ !tor_version_as_new_as(node->ri->platform, "0.3.0.8")) {
+ return 0;
+ }
+ }
+ return protocol_list_supports_protocol(node->ri->protocol_list,
+ PRT_HSDIR, PROTOVER_HSDIR_V3);
+ }
+ tor_assert_nonfatal_unreached_once();
+ return 0;
+}
+
+/** Return true iff <b>node</b> supports ed25519 authentication as an hidden
+ * service introduction point.*/
+int
+node_supports_ed25519_hs_intro(const node_t *node)
+{
+ tor_assert(node);
+
+ if (node->rs) {
+ return node->rs->supports_ed25519_hs_intro;
+ }
+ if (node->ri) {
+ if (node->ri->protocol_list == NULL) {
+ return 0;
+ }
+ return protocol_list_supports_protocol(node->ri->protocol_list,
+ PRT_HSINTRO, PROTOVER_HS_INTRO_V3);
+ }
+ tor_assert_nonfatal_unreached_once();
+ return 0;
+}
+
/** Return the RSA ID key's SHA1 digest for the provided node. */
const uint8_t *
node_get_rsa_id_digest(const node_t *node)
@@ -1606,8 +1762,8 @@ static char dir_info_status[512] = "";
* no exits in the consensus."
* To obtain the final weighted bandwidth, we multiply the
* weighted bandwidth fraction for each position (guard, middle, exit). */
-int
-router_have_minimum_dir_info(void)
+MOCK_IMPL(int,
+router_have_minimum_dir_info,(void))
{
static int logged_delay=0;
const char *delay_fetches_msg = NULL;
@@ -1654,6 +1810,7 @@ router_dir_info_changed(void)
{
need_to_update_have_min_dir_info = 1;
rend_hsdir_routers_changed();
+ hs_hsdir_set_changed_consider_reupload();
}
/** Return a string describing what we're missing before we have enough
diff --git a/src/or/nodelist.h b/src/or/nodelist.h
index 95ae778a5b..d16cf0ecf7 100644
--- a/src/or/nodelist.h
+++ b/src/or/nodelist.h
@@ -28,6 +28,8 @@ void nodelist_remove_routerinfo(routerinfo_t *ri);
void nodelist_purge(void);
smartlist_t *nodelist_find_nodes_with_microdesc(const microdesc_t *md);
+void nodelist_recompute_all_hsdir_indices(void);
+
void nodelist_free_all(void);
void nodelist_assert_ok(void);
@@ -58,6 +60,8 @@ const ed25519_public_key_t *node_get_ed25519_id(const node_t *node);
int node_ed25519_id_matches(const node_t *node,
const ed25519_public_key_t *id);
int node_supports_ed25519_link_authentication(const node_t *node);
+int node_supports_v3_hsdir(const node_t *node);
+int node_supports_ed25519_hs_intro(const node_t *node);
const uint8_t *node_get_rsa_id_digest(const node_t *node);
int node_has_ipv6_addr(const node_t *node);
@@ -103,7 +107,7 @@ int addrs_in_same_network_family(const tor_addr_t *a1,
* no exits in the consensus, we wait for enough info to create internal
* paths, and should avoid creating exit paths, as they will simply fail.
* We make sure we create all available circuit types at the same time. */
-int router_have_minimum_dir_info(void);
+MOCK_DECL(int, router_have_minimum_dir_info,(void));
/** Set to CONSENSUS_PATH_EXIT if there is at least one exit node
* in the consensus. We update this flag in compute_frac_paths_available if
diff --git a/src/or/ntmain.c b/src/or/ntmain.c
index e087bd6937..7a85ba9708 100644
--- a/src/or/ntmain.c
+++ b/src/or/ntmain.c
@@ -293,6 +293,7 @@ nt_service_body(int argc, char **argv)
* event loop */
service_status.dwCurrentState = SERVICE_RUNNING;
service_fns.SetServiceStatus_fn(hStatus, &service_status);
+ set_main_thread();
do_main_loop();
tor_cleanup();
}
@@ -328,9 +329,10 @@ nt_service_main(void)
case CMD_VERIFY_CONFIG:
case CMD_DUMP_CONFIG:
case CMD_KEYGEN:
+ case CMD_KEY_EXPIRATION:
log_err(LD_CONFIG, "Unsupported command (--list-fingerprint, "
- "--hash-password, --keygen, --dump-config, or --verify-config) "
- "in NT service.");
+ "--hash-password, --keygen, --dump-config, --verify-config, "
+ "or --key-expiration) in NT service.");
break;
case CMD_RUN_UNITTESTS:
default:
diff --git a/src/or/onion.c b/src/or/onion.c
index a98b97cb1d..7e1e89df1b 100644
--- a/src/or/onion.c
+++ b/src/or/onion.c
@@ -1219,7 +1219,7 @@ extend_cell_format(uint8_t *command_out, uint16_t *len_out,
*command_out = RELAY_COMMAND_EXTEND;
*len_out = 6 + TAP_ONIONSKIN_CHALLENGE_LEN + DIGEST_LEN;
set_uint32(p, tor_addr_to_ipv4n(&cell_in->orport_ipv4.addr));
- set_uint16(p+4, ntohs(cell_in->orport_ipv4.port));
+ set_uint16(p+4, htons(cell_in->orport_ipv4.port));
if (cell_in->create_cell.handshake_type == ONION_HANDSHAKE_TYPE_NTOR) {
memcpy(p+6, NTOR_CREATE_MAGIC, 16);
memcpy(p+22, cell_in->create_cell.onionskin, NTOR_ONIONSKIN_LEN);
diff --git a/src/or/onion_tap.c b/src/or/onion_tap.c
index 294fc0df6d..c71fa236ed 100644
--- a/src/or/onion_tap.c
+++ b/src/or/onion_tap.c
@@ -72,10 +72,8 @@ onion_skin_TAP_create(crypto_pk_t *dest_router_key,
if (crypto_dh_get_public(dh, challenge, dhbytes))
goto err;
- note_crypto_pk_op(ENC_ONIONSKIN);
-
/* set meeting point, meeting cookie, etc here. Leave zero for now. */
- if (crypto_pk_public_hybrid_encrypt(dest_router_key, onion_skin_out,
+ if (crypto_pk_obsolete_public_hybrid_encrypt(dest_router_key, onion_skin_out,
TAP_ONIONSKIN_CHALLENGE_LEN,
challenge, DH_KEY_LEN,
PK_PKCS1_OAEP_PADDING, 1)<0)
@@ -124,8 +122,7 @@ onion_skin_TAP_server_handshake(
k = i==0?private_key:prev_private_key;
if (!k)
break;
- note_crypto_pk_op(DEC_ONIONSKIN);
- len = crypto_pk_private_hybrid_decrypt(k, challenge,
+ len = crypto_pk_obsolete_private_hybrid_decrypt(k, challenge,
TAP_ONIONSKIN_CHALLENGE_LEN,
onion_skin,
TAP_ONIONSKIN_CHALLENGE_LEN,
diff --git a/src/or/or.h b/src/or/or.h
index 77207bc031..5d55094a02 100644
--- a/src/or/or.h
+++ b/src/or/or.h
@@ -421,15 +421,23 @@ typedef enum {
#define DIR_PURPOSE_FETCH_RENDDESC_V2 18
/** A connection to a directory server: download a microdescriptor. */
#define DIR_PURPOSE_FETCH_MICRODESC 19
-#define DIR_PURPOSE_MAX_ 19
+/** A connection to a hidden service directory: upload a v3 descriptor. */
+#define DIR_PURPOSE_UPLOAD_HSDESC 20
+/** A connection to a hidden service directory: fetch a v3 descriptor. */
+#define DIR_PURPOSE_FETCH_HSDESC 21
+/** A connection to a directory server: set after a hidden service descriptor
+ * is downloaded. */
+#define DIR_PURPOSE_HAS_FETCHED_HSDESC 22
+#define DIR_PURPOSE_MAX_ 22
/** True iff <b>p</b> is a purpose corresponding to uploading
* data to a directory server. */
#define DIR_PURPOSE_IS_UPLOAD(p) \
((p)==DIR_PURPOSE_UPLOAD_DIR || \
(p)==DIR_PURPOSE_UPLOAD_VOTE || \
- (p)==DIR_PURPOSE_UPLOAD_SIGNATURES || \
- (p)==DIR_PURPOSE_UPLOAD_RENDDESC_V2)
+ (p)==DIR_PURPOSE_UPLOAD_SIGNATURES || \
+ (p)==DIR_PURPOSE_UPLOAD_RENDDESC_V2 || \
+ (p)==DIR_PURPOSE_UPLOAD_HSDESC)
#define EXIT_PURPOSE_MIN_ 1
/** This exit stream wants to do an ordinary connect. */
@@ -846,6 +854,13 @@ rend_data_v2_t *TO_REND_DATA_V2(const rend_data_t *d)
return DOWNCAST(rend_data_v2_t, d);
}
+/* Stub because we can't include hs_ident.h. */
+struct hs_ident_edge_conn_t;
+struct hs_ident_dir_conn_t;
+struct hs_ident_circuit_t;
+/* Stub because we can't include hs_common.h. */
+struct hsdir_index_t;
+
/** Time interval for tracking replays of DH public keys received in
* INTRODUCE2 cells. Used only to avoid launching multiple
* simultaneous attempts to connect to the same rendezvous point. */
@@ -1633,6 +1648,11 @@ typedef struct edge_connection_t {
* an exit)? */
rend_data_t *rend_data;
+ /* Hidden service connection identifier for edge connections. Used by the HS
+ * client-side code to identify client SOCKS connections and by the
+ * service-side code to match HS circuits with their streams. */
+ struct hs_ident_edge_conn_t *hs_ident;
+
uint32_t address_ttl; /**< TTL for address-to-addr mapping on exit
* connection. Exit connections only. */
uint32_t begincell_flags; /** Flags sent or received in the BEGIN cell
@@ -1783,6 +1803,11 @@ typedef struct dir_connection_t {
/** What rendezvous service are we querying for? */
rend_data_t *rend_data;
+ /* Hidden service connection identifier for dir connections: Used by HS
+ client-side code to fetch HS descriptors, and by the service-side code to
+ upload descriptors. */
+ struct hs_ident_dir_conn_t *hs_ident;
+
/** If this is a one-hop connection, tracks the state of the directory guard
* for this connection (if any). */
struct circuit_guard_state_t *guard_state;
@@ -2058,7 +2083,9 @@ typedef struct download_status_t {
* or after each failure? */
download_schedule_backoff_bitfield_t backoff : 1; /**< do we use the
* deterministic schedule, or random
- * exponential backoffs? */
+ * exponential backoffs?
+ * Increment on failure schedules
+ * always use exponential backoff. */
uint8_t last_backoff_position; /**< number of attempts/failures, depending
* on increment_on, when we last recalculated
* the delay. Only updated if backoff
@@ -2473,6 +2500,10 @@ typedef struct node_t {
time_t last_reachable; /* IPv4. */
time_t last_reachable6; /* IPv6. */
+ /* Hidden service directory index data. This is used by a service or client
+ * in order to know what's the hs directory index for this node at the time
+ * the consensus is set. */
+ struct hsdir_index_t *hsdir_index;
} node_t;
/** Linked list of microdesc hash lines for a single router in a directory
@@ -3186,6 +3217,10 @@ typedef struct origin_circuit_t {
/** Holds all rendezvous data on either client or service side. */
rend_data_t *rend_data;
+ /** Holds hidden service identifier on either client or service side. This
+ * is for both introduction and rendezvous circuit. */
+ struct hs_ident_circuit_t *hs_ident;
+
/** Holds the data that the entry guard system uses to track the
* status of the guard this circuit is using, and thereby to determine
* whether this circuit can be used. */
@@ -3567,7 +3602,8 @@ typedef struct {
enum {
CMD_RUN_TOR=0, CMD_LIST_FINGERPRINT, CMD_HASH_PASSWORD,
CMD_VERIFY_CONFIG, CMD_RUN_UNITTESTS, CMD_DUMP_CONFIG,
- CMD_KEYGEN
+ CMD_KEYGEN,
+ CMD_KEY_EXPIRATION,
} command;
char *command_arg; /**< Argument for command-line option. */
@@ -4563,6 +4599,10 @@ typedef struct {
* consensuses around so that we can generate diffs from them. If 0,
* use the default. */
int MaxConsensusAgeForDiffs;
+
+ /** Bool (default: 0). Tells Tor to never try to exec another program.
+ */
+ int NoExec;
} or_options_t;
/** Persistent state for an onion router, as saved to disk. */
@@ -4594,6 +4634,9 @@ typedef struct {
config_line_t *TransportProxies;
+ /** Cached revision counters for active hidden services on this host */
+ config_line_t *HidServRevCounter;
+
/** These fields hold information on the history of bandwidth usage for
* servers. The "Ends" fields hold the time when we last updated the
* bandwidth usage. The "Interval" fields hold the granularity, in seconds,
diff --git a/src/or/parsecommon.c b/src/or/parsecommon.c
index 7959867875..6b5359303a 100644
--- a/src/or/parsecommon.c
+++ b/src/or/parsecommon.c
@@ -436,7 +436,7 @@ find_opt_by_keyword(smartlist_t *s, directory_keyword keyword)
* in the same order in which they occur in <b>s</b>. Otherwise return
* NULL. */
smartlist_t *
-find_all_by_keyword(smartlist_t *s, directory_keyword k)
+find_all_by_keyword(const smartlist_t *s, directory_keyword k)
{
smartlist_t *out = NULL;
SMARTLIST_FOREACH(s, directory_token_t *, t,
diff --git a/src/or/parsecommon.h b/src/or/parsecommon.h
index b9f1613457..5e5f9f4db6 100644
--- a/src/or/parsecommon.h
+++ b/src/or/parsecommon.h
@@ -160,6 +160,7 @@ typedef enum {
R3_INTRO_AUTH_REQUIRED,
R3_SINGLE_ONION_SERVICE,
R3_INTRODUCTION_POINT,
+ R3_INTRO_ONION_KEY,
R3_INTRO_AUTH_KEY,
R3_INTRO_ENC_KEY,
R3_INTRO_ENC_KEY_CERT,
@@ -315,7 +316,7 @@ directory_token_t *find_by_keyword_(smartlist_t *s,
directory_token_t *find_opt_by_keyword(smartlist_t *s,
directory_keyword keyword);
-smartlist_t * find_all_by_keyword(smartlist_t *s, directory_keyword k);
+smartlist_t * find_all_by_keyword(const smartlist_t *s, directory_keyword k);
#endif /* TOR_PARSECOMMON_H */
diff --git a/src/or/policies.c b/src/or/policies.c
index 3d49a6110c..4c24bfbc32 100644
--- a/src/or/policies.c
+++ b/src/or/policies.c
@@ -2731,7 +2731,7 @@ parse_short_policy(const char *summary)
}
{
- size_t size = STRUCT_OFFSET(short_policy_t, entries) +
+ size_t size = offsetof(short_policy_t, entries) +
sizeof(short_policy_entry_t)*(n_entries);
result = tor_malloc_zero(size);
diff --git a/src/or/protover.h b/src/or/protover.h
index 22667bed79..2066aeec72 100644
--- a/src/or/protover.h
+++ b/src/or/protover.h
@@ -17,6 +17,11 @@
/* This is a guess. */
#define FIRST_TOR_VERSION_TO_ADVERTISE_PROTOCOLS "0.2.9.3-alpha"
+/** The protover version number that signifies HSDir support for HSv3 */
+#define PROTOVER_HSDIR_V3 2
+/** The protover version number that signifies HSv3 intro point support */
+#define PROTOVER_HS_INTRO_V3 4
+
/** List of recognized subprotocols. */
typedef enum protocol_type_t {
PRT_LINK,
diff --git a/src/or/relay.c b/src/or/relay.c
index 0ff53ed5e9..18ccc65b80 100644
--- a/src/or/relay.c
+++ b/src/or/relay.c
@@ -184,18 +184,12 @@ relay_digest_matches(crypto_digest_t *digest, cell_t *cell)
/** Apply <b>cipher</b> to CELL_PAYLOAD_SIZE bytes of <b>in</b>
* (in place).
*
- * If <b>encrypt_mode</b> is 1 then encrypt, else decrypt.
- *
- * Returns 0.
+ * Note that we use the same operation for encrypting and for decrypting.
*/
-static int
-relay_crypt_one_payload(crypto_cipher_t *cipher, uint8_t *in,
- int encrypt_mode)
+static void
+relay_crypt_one_payload(crypto_cipher_t *cipher, uint8_t *in)
{
- (void)encrypt_mode;
crypto_cipher_crypt_inplace(cipher, (char*) in, CELL_PAYLOAD_SIZE);
-
- return 0;
}
/**
@@ -449,8 +443,8 @@ relay_crypt(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction,
do { /* Remember: cpath is in forward order, that is, first hop first. */
tor_assert(thishop);
- if (relay_crypt_one_payload(thishop->b_crypto, cell->payload, 0) < 0)
- return -1;
+ /* decrypt one layer */
+ relay_crypt_one_payload(thishop->b_crypto, cell->payload);
relay_header_unpack(&rh, cell->payload);
if (rh.recognized == 0) {
@@ -467,19 +461,14 @@ relay_crypt(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction,
log_fn(LOG_PROTOCOL_WARN, LD_OR,
"Incoming cell at client not recognized. Closing.");
return -1;
- } else { /* we're in the middle. Just one crypt. */
- if (relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->p_crypto,
- cell->payload, 1) < 0)
- return -1;
-// log_fn(LOG_DEBUG,"Skipping recognized check, because we're not "
-// "the client.");
+ } else {
+ /* We're in the middle. Encrypt one layer. */
+ relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->p_crypto, cell->payload);
}
} else /* cell_direction == CELL_DIRECTION_OUT */ {
- /* we're in the middle. Just one crypt. */
+ /* We're in the middle. Decrypt one layer. */
- if (relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->n_crypto,
- cell->payload, 0) < 0)
- return -1;
+ relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->n_crypto, cell->payload);
relay_header_unpack(&rh, cell->payload);
if (rh.recognized == 0) {
@@ -525,11 +514,8 @@ circuit_package_relay_cell(cell_t *cell, circuit_t *circ,
/* moving from farthest to nearest hop */
do {
tor_assert(thishop);
- /* XXXX RD This is a bug, right? */
- log_debug(LD_OR,"crypting a layer of the relay cell.");
- if (relay_crypt_one_payload(thishop->f_crypto, cell->payload, 1) < 0) {
- return -1;
- }
+ log_debug(LD_OR,"encrypting a layer of the relay cell.");
+ relay_crypt_one_payload(thishop->f_crypto, cell->payload);
thishop = thishop->prev;
} while (thishop != TO_ORIGIN_CIRCUIT(circ)->cpath->prev);
@@ -546,8 +532,8 @@ circuit_package_relay_cell(cell_t *cell, circuit_t *circ,
or_circ = TO_OR_CIRCUIT(circ);
chan = or_circ->p_chan;
relay_set_digest(or_circ->p_digest, cell);
- if (relay_crypt_one_payload(or_circ->p_crypto, cell->payload, 1) < 0)
- return -1;
+ /* encrypt one layer */
+ relay_crypt_one_payload(or_circ->p_crypto, cell->payload);
}
++stats_n_relay_cells_relayed;
diff --git a/src/or/rendcache.c b/src/or/rendcache.c
index 11b60b36a1..792f6a1302 100644
--- a/src/or/rendcache.c
+++ b/src/or/rendcache.c
@@ -512,7 +512,7 @@ rend_cache_lookup_entry(const char *query, int version, rend_cache_entry_t **e)
tor_assert(rend_cache);
tor_assert(query);
- if (!rend_valid_service_id(query)) {
+ if (!rend_valid_v2_service_id(query)) {
ret = -EINVAL;
goto end;
}
@@ -558,7 +558,7 @@ rend_cache_lookup_v2_desc_as_service(const char *query, rend_cache_entry_t **e)
tor_assert(rend_cache_local_service);
tor_assert(query);
- if (!rend_valid_service_id(query)) {
+ if (!rend_valid_v2_service_id(query)) {
ret = -EINVAL;
goto end;
}
diff --git a/src/or/rendclient.c b/src/or/rendclient.c
index 9bc2d6289d..55f79c50fb 100644
--- a/src/or/rendclient.c
+++ b/src/or/rendclient.c
@@ -17,6 +17,8 @@
#include "connection_edge.h"
#include "directory.h"
#include "hs_common.h"
+#include "hs_circuit.h"
+#include "hs_client.h"
#include "main.h"
#include "networkstatus.h"
#include "nodelist.h"
@@ -41,7 +43,7 @@ rend_client_purge_state(void)
rend_cache_purge();
rend_cache_failure_purge();
rend_client_cancel_descriptor_fetches();
- rend_client_purge_last_hid_serv_requests();
+ hs_purge_last_hid_serv_requests();
}
/** Called when we've established a circuit to an introduction point:
@@ -88,46 +90,6 @@ rend_client_send_establish_rendezvous(origin_circuit_t *circ)
return 0;
}
-/** Extend the introduction circuit <b>circ</b> to another valid
- * introduction point for the hidden service it is trying to connect
- * to, or mark it and launch a new circuit if we can't extend it.
- * Return 0 on success or possible success. Return -1 and mark the
- * introduction circuit for close on permanent failure.
- *
- * On failure, the caller is responsible for marking the associated
- * rendezvous circuit for close. */
-static int
-rend_client_reextend_intro_circuit(origin_circuit_t *circ)
-{
- extend_info_t *extend_info;
- int result;
- extend_info = rend_client_get_random_intro(circ->rend_data);
- if (!extend_info) {
- log_warn(LD_REND,
- "No usable introduction points left for %s. Closing.",
- safe_str_client(rend_data_get_address(circ->rend_data)));
- circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
- return -1;
- }
- // XXX: should we not re-extend if hs_circ_has_timed_out?
- if (circ->remaining_relay_early_cells) {
- log_info(LD_REND,
- "Re-extending circ %u, this time to %s.",
- (unsigned)circ->base_.n_circ_id,
- safe_str_client(extend_info_describe(extend_info)));
- result = circuit_extend_to_new_exit(circ, extend_info);
- } else {
- log_info(LD_REND,
- "Closing intro circ %u (out of RELAY_EARLY cells).",
- (unsigned)circ->base_.n_circ_id);
- circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED);
- /* connection_ap_handshake_attach_circuit will launch a new intro circ. */
- result = 0;
- }
- extend_info_free(extend_info);
- return result;
-}
-
/** Called when we're trying to connect an ap conn; sends an INTRODUCE1 cell
* down introcirc if possible.
*/
@@ -201,7 +163,7 @@ rend_client_send_introduction(origin_circuit_t *introcirc,
introcirc->build_state->chosen_exit)),
smartlist_len(entry->parsed->intro_nodes));
- if (rend_client_reextend_intro_circuit(introcirc)) {
+ if (hs_client_reextend_intro_circuit(introcirc)) {
status = -2;
goto perm_err;
} else {
@@ -265,6 +227,11 @@ rend_client_send_introduction(origin_circuit_t *introcirc,
klen = crypto_pk_asn1_encode(extend_info->onion_key,
tmp+v3_shift+7+DIGEST_LEN+2,
sizeof(tmp)-(v3_shift+7+DIGEST_LEN+2));
+ if (klen < 0) {
+ log_warn(LD_BUG,"Internal error: can't encode public key.");
+ status = -2;
+ goto perm_err;
+ }
set_uint16(tmp+v3_shift+7+DIGEST_LEN, htons(klen));
memcpy(tmp+v3_shift+7+DIGEST_LEN+2+klen, rendcirc->rend_data->rend_cookie,
REND_COOKIE_LEN);
@@ -285,10 +252,9 @@ rend_client_send_introduction(origin_circuit_t *introcirc,
goto perm_err;
}
- note_crypto_pk_op(REND_CLIENT);
- /*XXX maybe give crypto_pk_public_hybrid_encrypt a max_len arg,
+ /*XXX maybe give crypto_pk_obsolete_public_hybrid_encrypt a max_len arg,
* to avoid buffer overflows? */
- r = crypto_pk_public_hybrid_encrypt(intro_key, payload+DIGEST_LEN,
+ r = crypto_pk_obsolete_public_hybrid_encrypt(intro_key, payload+DIGEST_LEN,
sizeof(payload)-DIGEST_LEN,
tmp,
(int)(dh_offset+DH_KEY_LEN),
@@ -391,23 +357,11 @@ rend_client_introduction_acked(origin_circuit_t *circ,
origin_circuit_t *rendcirc;
(void) request; // XXXX Use this.
- if (circ->base_.purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) {
- log_warn(LD_PROTOCOL,
- "Received REND_INTRODUCE_ACK on unexpected circuit %u.",
- (unsigned)circ->base_.n_circ_id);
- circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
- return -1;
- }
-
tor_assert(circ->build_state);
tor_assert(circ->build_state->chosen_exit);
assert_circ_anonymity_ok(circ, options);
tor_assert(circ->rend_data);
- /* For path bias: This circuit was used successfully. Valid
- * nacks and acks count. */
- pathbias_mark_use_success(circ);
-
if (request_len == 0) {
/* It's an ACK; the introduction point relayed our introduction request. */
/* Locate the rend circ which is waiting to hear about this ack,
@@ -449,7 +403,7 @@ rend_client_introduction_acked(origin_circuit_t *circ,
INTRO_POINT_FAILURE_GENERIC)>0) {
/* There are introduction points left. Re-extend the circuit to
* another intro point and try again. */
- int result = rend_client_reextend_intro_circuit(circ);
+ int result = hs_client_reextend_intro_circuit(circ);
/* XXXX If that call failed, should we close the rend circuit,
* too? */
return result;
@@ -465,230 +419,6 @@ rend_client_introduction_acked(origin_circuit_t *circ,
return 0;
}
-/** The period for which a hidden service directory cannot be queried for
- * the same descriptor ID again. */
-#define REND_HID_SERV_DIR_REQUERY_PERIOD (15 * 60)
-/** Test networks generate a new consensus every 5 or 10 seconds.
- * So allow them to requery HSDirs much faster. */
-#define REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING (5)
-
-/** Return the period for which a hidden service directory cannot be queried
- * for the same descriptor ID again, taking TestingTorNetwork into account. */
-static time_t
-hsdir_requery_period(const or_options_t *options)
-{
- tor_assert(options);
-
- if (options->TestingTorNetwork) {
- return REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING;
- } else {
- return REND_HID_SERV_DIR_REQUERY_PERIOD;
- }
-}
-
-/** Contains the last request times to hidden service directories for
- * certain queries; each key is a string consisting of the
- * concatenation of a base32-encoded HS directory identity digest and
- * base32-encoded HS descriptor ID; each value is a pointer to a time_t
- * holding the time of the last request for that descriptor ID to that
- * HS directory. */
-static strmap_t *last_hid_serv_requests_ = NULL;
-
-/** Returns last_hid_serv_requests_, initializing it to a new strmap if
- * necessary. */
-static strmap_t *
-get_last_hid_serv_requests(void)
-{
- if (!last_hid_serv_requests_)
- last_hid_serv_requests_ = strmap_new();
- return last_hid_serv_requests_;
-}
-
-#define LAST_HID_SERV_REQUEST_KEY_LEN (REND_DESC_ID_V2_LEN_BASE32 + \
- REND_DESC_ID_V2_LEN_BASE32)
-
-/** Look up the last request time to hidden service directory <b>hs_dir</b>
- * for descriptor ID <b>desc_id_base32</b>. If <b>set</b> is non-zero,
- * assign the current time <b>now</b> and return that. Otherwise, return the
- * most recent request time, or 0 if no such request has been sent before.
- */
-static time_t
-lookup_last_hid_serv_request(routerstatus_t *hs_dir,
- const char *desc_id_base32,
- time_t now, int set)
-{
- char hsdir_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
- char hsdir_desc_comb_id[LAST_HID_SERV_REQUEST_KEY_LEN + 1];
- time_t *last_request_ptr;
- strmap_t *last_hid_serv_requests = get_last_hid_serv_requests();
- base32_encode(hsdir_id_base32, sizeof(hsdir_id_base32),
- hs_dir->identity_digest, DIGEST_LEN);
- tor_snprintf(hsdir_desc_comb_id, sizeof(hsdir_desc_comb_id), "%s%s",
- hsdir_id_base32,
- desc_id_base32);
- /* XXX++?? tor_assert(strlen(hsdir_desc_comb_id) ==
- LAST_HID_SERV_REQUEST_KEY_LEN); */
- if (set) {
- time_t *oldptr;
- last_request_ptr = tor_malloc_zero(sizeof(time_t));
- *last_request_ptr = now;
- oldptr = strmap_set(last_hid_serv_requests, hsdir_desc_comb_id,
- last_request_ptr);
- tor_free(oldptr);
- } else
- last_request_ptr = strmap_get_lc(last_hid_serv_requests,
- hsdir_desc_comb_id);
- return (last_request_ptr) ? *last_request_ptr : 0;
-}
-
-/** Clean the history of request times to hidden service directories, so that
- * it does not contain requests older than REND_HID_SERV_DIR_REQUERY_PERIOD
- * seconds any more. */
-static void
-directory_clean_last_hid_serv_requests(time_t now)
-{
- strmap_iter_t *iter;
- time_t cutoff = now - hsdir_requery_period(get_options());
- strmap_t *last_hid_serv_requests = get_last_hid_serv_requests();
- for (iter = strmap_iter_init(last_hid_serv_requests);
- !strmap_iter_done(iter); ) {
- const char *key;
- void *val;
- time_t *ent;
- strmap_iter_get(iter, &key, &val);
- ent = (time_t *) val;
- if (*ent < cutoff) {
- iter = strmap_iter_next_rmv(last_hid_serv_requests, iter);
- tor_free(ent);
- } else {
- iter = strmap_iter_next(last_hid_serv_requests, iter);
- }
- }
-}
-
-/** Remove all requests related to the descriptor ID <b>desc_id</b> from the
- * history of times of requests to hidden service directories.
- * <b>desc_id</b> is an unencoded descriptor ID of size DIGEST_LEN.
- *
- * This is called from rend_client_note_connection_attempt_ended(), which
- * must be idempotent, so any future changes to this function must leave it
- * idempotent too. */
-static void
-purge_hid_serv_from_last_hid_serv_requests(const char *desc_id)
-{
- strmap_iter_t *iter;
- strmap_t *last_hid_serv_requests = get_last_hid_serv_requests();
- char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
-
- /* Key is stored with the base32 encoded desc_id. */
- base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id,
- DIGEST_LEN);
- for (iter = strmap_iter_init(last_hid_serv_requests);
- !strmap_iter_done(iter); ) {
- const char *key;
- void *val;
- strmap_iter_get(iter, &key, &val);
- /* XXX++?? tor_assert(strlen(key) == LAST_HID_SERV_REQUEST_KEY_LEN); */
- if (tor_memeq(key + LAST_HID_SERV_REQUEST_KEY_LEN -
- REND_DESC_ID_V2_LEN_BASE32,
- desc_id_base32,
- REND_DESC_ID_V2_LEN_BASE32)) {
- iter = strmap_iter_next_rmv(last_hid_serv_requests, iter);
- tor_free(val);
- } else {
- iter = strmap_iter_next(last_hid_serv_requests, iter);
- }
- }
-}
-
-/** Purge the history of request times to hidden service directories,
- * so that future lookups of an HS descriptor will not fail because we
- * accessed all of the HSDir relays responsible for the descriptor
- * recently. */
-void
-rend_client_purge_last_hid_serv_requests(void)
-{
- /* Don't create the table if it doesn't exist yet (and it may very
- * well not exist if the user hasn't accessed any HSes)... */
- strmap_t *old_last_hid_serv_requests = last_hid_serv_requests_;
- /* ... and let get_last_hid_serv_requests re-create it for us if
- * necessary. */
- last_hid_serv_requests_ = NULL;
-
- if (old_last_hid_serv_requests != NULL) {
- log_info(LD_REND, "Purging client last-HS-desc-request-time table");
- strmap_free(old_last_hid_serv_requests, tor_free_);
- }
-}
-
-/** This returns a good valid hs dir that should be used for the given
- * descriptor id.
- *
- * Return NULL on error else the hsdir node pointer. */
-static routerstatus_t *
-pick_hsdir(const char *desc_id, const char *desc_id_base32)
-{
- smartlist_t *responsible_dirs = smartlist_new();
- smartlist_t *usable_responsible_dirs = smartlist_new();
- const or_options_t *options = get_options();
- routerstatus_t *hs_dir;
- time_t now = time(NULL);
- int excluded_some;
-
- tor_assert(desc_id);
- tor_assert(desc_id_base32);
-
- /* Determine responsible dirs. Even if we can't get all we want, work with
- * the ones we have. If it's empty, we'll notice below. */
- hid_serv_get_responsible_directories(responsible_dirs, desc_id);
-
- /* Clean request history first. */
- directory_clean_last_hid_serv_requests(now);
-
- /* Only select those hidden service directories to which we did not send a
- * request recently and for which we have a router descriptor here. */
- SMARTLIST_FOREACH_BEGIN(responsible_dirs, routerstatus_t *, dir) {
- time_t last = lookup_last_hid_serv_request(dir, desc_id_base32,
- 0, 0);
- const node_t *node = node_get_by_id(dir->identity_digest);
- if (last + hsdir_requery_period(options) >= now ||
- !node || !node_has_descriptor(node)) {
- SMARTLIST_DEL_CURRENT(responsible_dirs, dir);
- continue;
- }
- if (!routerset_contains_node(options->ExcludeNodes, node)) {
- smartlist_add(usable_responsible_dirs, dir);
- }
- } SMARTLIST_FOREACH_END(dir);
-
- excluded_some =
- smartlist_len(usable_responsible_dirs) < smartlist_len(responsible_dirs);
-
- hs_dir = smartlist_choose(usable_responsible_dirs);
- if (!hs_dir && !options->StrictNodes) {
- hs_dir = smartlist_choose(responsible_dirs);
- }
-
- smartlist_free(responsible_dirs);
- smartlist_free(usable_responsible_dirs);
- if (!hs_dir) {
- log_info(LD_REND, "Could not pick one of the responsible hidden "
- "service directories, because we requested them all "
- "recently without success.");
- if (options->StrictNodes && excluded_some) {
- log_warn(LD_REND, "Could not pick a hidden service directory for the "
- "requested hidden service: they are all either down or "
- "excluded, and StrictNodes is set.");
- }
- } else {
- /* Remember that we are requesting a descriptor from this hidden service
- * directory now. */
- lookup_last_hid_serv_request(hs_dir, desc_id_base32, now, 1);
- }
-
- return hs_dir;
-}
-
/** Determine the responsible hidden service directories for <b>desc_id</b>
* and fetch the descriptor with that ID from one of them. Only
* send a request to a hidden service directory that we have not yet tried
@@ -721,7 +451,12 @@ directory_get_from_hs_dir(const char *desc_id,
/* Automatically pick an hs dir if none given. */
if (!rs_hsdir) {
- hs_dir = pick_hsdir(desc_id, desc_id_base32);
+ /* Determine responsible dirs. Even if we can't get all we want, work with
+ * the ones we have. If it's empty, we'll notice in hs_pick_hsdir(). */
+ smartlist_t *responsible_dirs = smartlist_new();
+ hid_serv_get_responsible_directories(responsible_dirs, desc_id);
+
+ hs_dir = hs_pick_hsdir(responsible_dirs, desc_id_base32);
if (!hs_dir) {
/* No suitable hs dir can be found, stop right now. */
control_event_hs_descriptor_failed(rend_query, NULL, "QUERY_NO_HSDIR");
@@ -786,6 +521,20 @@ directory_get_from_hs_dir(const char *desc_id,
return 1;
}
+/** Remove tracked HSDir requests from our history for this hidden service
+ * descriptor <b>desc_id</b> (of size DIGEST_LEN) */
+static void
+purge_v2_hidserv_req(const char *desc_id)
+{
+ char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
+
+ /* The hsdir request tracker stores v2 keys using the base32 encoded
+ desc_id. Do it: */
+ base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id,
+ DIGEST_LEN);
+ hs_purge_hid_serv_from_last_hid_serv_requests(desc_id_base32);
+}
+
/** Fetch a v2 descriptor using the given descriptor id. If any hsdir(s) are
* given, they will be used instead.
*
@@ -860,8 +609,7 @@ fetch_v2_desc_by_addr(rend_data_t *rend_query, smartlist_t *hsdirs)
sizeof(descriptor_id)) != 0) {
/* Not equal from what we currently have so purge the last hid serv
* request cache and update the descriptor ID with the new value. */
- purge_hid_serv_from_last_hid_serv_requests(
- rend_data->descriptor_id[chosen_replica]);
+ purge_v2_hidserv_req(rend_data->descriptor_id[chosen_replica]);
memcpy(rend_data->descriptor_id[chosen_replica], descriptor_id,
sizeof(rend_data->descriptor_id[chosen_replica]));
}
@@ -1107,118 +855,24 @@ rend_client_report_intro_point_failure(extend_info_t *failed_intro,
return 1;
}
-/** Called when we receive a RENDEZVOUS_ESTABLISHED cell; changes the state of
- * the circuit to C_REND_READY.
- */
-int
-rend_client_rendezvous_acked(origin_circuit_t *circ, const uint8_t *request,
- size_t request_len)
-{
- (void) request;
- (void) request_len;
- /* we just got an ack for our establish-rendezvous. switch purposes. */
- if (circ->base_.purpose != CIRCUIT_PURPOSE_C_ESTABLISH_REND) {
- log_warn(LD_PROTOCOL,"Got a rendezvous ack when we weren't expecting one. "
- "Closing circ.");
- circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
- return -1;
- }
- log_info(LD_REND,"Got rendezvous ack. This circuit is now ready for "
- "rendezvous.");
- circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_REND_READY);
- /* Set timestamp_dirty, because circuit_expire_building expects it
- * to specify when a circuit entered the _C_REND_READY state. */
- circ->base_.timestamp_dirty = time(NULL);
-
- /* From a path bias point of view, this circuit is now successfully used.
- * Waiting any longer opens us up to attacks from malicious hidden services.
- * They could induce the client to attempt to connect to their hidden
- * service and never reply to the client's rend requests */
- pathbias_mark_use_success(circ);
-
- /* XXXX++ This is a pretty brute-force approach. It'd be better to
- * attach only the connections that are waiting on this circuit, rather
- * than trying to attach them all. See comments bug 743. */
- /* If we already have the introduction circuit built, make sure we send
- * the INTRODUCE cell _now_ */
- connection_ap_attach_pending(1);
- return 0;
-}
-
/** The service sent us a rendezvous cell; join the circuits. */
int
rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request,
size_t request_len)
{
- crypt_path_t *hop;
- char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN];
-
- if ((circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY &&
- circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED)
- || !circ->build_state->pending_final_cpath) {
- log_warn(LD_PROTOCOL,"Got rendezvous2 cell from hidden service, but not "
- "expecting it. Closing.");
- circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
- return -1;
- }
-
if (request_len != DH_KEY_LEN+DIGEST_LEN) {
log_warn(LD_PROTOCOL,"Incorrect length (%d) on RENDEZVOUS2 cell.",
(int)request_len);
goto err;
}
- log_info(LD_REND,"Got RENDEZVOUS2 cell from hidden service.");
-
- /* first DH_KEY_LEN bytes are g^y from the service. Finish the dh
- * handshake...*/
- tor_assert(circ->build_state);
- tor_assert(circ->build_state->pending_final_cpath);
- hop = circ->build_state->pending_final_cpath;
- tor_assert(hop->rend_dh_handshake_state);
- if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN,
- hop->rend_dh_handshake_state, (char*)request,
- DH_KEY_LEN,
- keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) {
- log_warn(LD_GENERAL, "Couldn't complete DH handshake.");
+ if (hs_circuit_setup_e2e_rend_circ_legacy_client(circ, request) < 0) {
+ log_warn(LD_GENERAL, "Failed to setup circ");
goto err;
}
- /* ... and set up cpath. */
- if (circuit_init_cpath_crypto(hop, keys+DIGEST_LEN, 0)<0)
- goto err;
-
- /* Check whether the digest is right... */
- if (tor_memneq(keys, request+DH_KEY_LEN, DIGEST_LEN)) {
- log_warn(LD_PROTOCOL, "Incorrect digest of key material.");
- goto err;
- }
-
- crypto_dh_free(hop->rend_dh_handshake_state);
- hop->rend_dh_handshake_state = NULL;
-
- /* All is well. Extend the circuit. */
- circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_REND_JOINED);
- hop->state = CPATH_STATE_OPEN;
- /* set the windows to default. these are the windows
- * that the client thinks the service has.
- */
- hop->package_window = circuit_initial_package_window();
- hop->deliver_window = CIRCWINDOW_START;
-
- /* Now that this circuit has finished connecting to its destination,
- * make sure circuit_get_open_circ_or_launch is willing to return it
- * so we can actually use it. */
- circ->hs_circ_has_timed_out = 0;
-
- onion_append_to_cpath(&circ->cpath, hop);
- circ->build_state->pending_final_cpath = NULL; /* prevent double-free */
-
- circuit_try_attaching_streams(circ);
-
- memwipe(keys, 0, sizeof(keys));
return 0;
+
err:
- memwipe(keys, 0, sizeof(keys));
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
return -1;
}
@@ -1305,14 +959,14 @@ rend_client_note_connection_attempt_ended(const rend_data_t *rend_data)
for (replica = 0; replica < ARRAY_LENGTH(rend_data_v2->descriptor_id);
replica++) {
const char *desc_id = rend_data_v2->descriptor_id[replica];
- purge_hid_serv_from_last_hid_serv_requests(desc_id);
+ purge_v2_hidserv_req(desc_id);
}
log_info(LD_REND, "Connection attempt for %s has ended; "
"cleaning up temporary state.",
safe_str_client(onion_address));
} else {
/* We only have an ID for a fetch. Probably used by HSFETCH. */
- purge_hid_serv_from_last_hid_serv_requests(rend_data_v2->desc_id_fetch);
+ purge_v2_hidserv_req(rend_data_v2->desc_id_fetch);
}
}
@@ -1511,7 +1165,7 @@ rend_parse_service_authorization(const or_options_t *options,
goto err;
}
strlcpy(auth->onion_address, onion_address, REND_SERVICE_ID_LEN_BASE32+1);
- if (!rend_valid_service_id(auth->onion_address)) {
+ if (!rend_valid_v2_service_id(auth->onion_address)) {
log_warn(LD_CONFIG, "Onion address has wrong format: '%s'",
onion_address);
goto err;
diff --git a/src/or/rendclient.h b/src/or/rendclient.h
index ff0f4084fd..ac0503bade 100644
--- a/src/or/rendclient.h
+++ b/src/or/rendclient.h
@@ -24,15 +24,11 @@ int rend_client_introduction_acked(origin_circuit_t *circ,
void rend_client_refetch_v2_renddesc(rend_data_t *rend_query);
int rend_client_fetch_v2_desc(rend_data_t *query, smartlist_t *hsdirs);
void rend_client_cancel_descriptor_fetches(void);
-void rend_client_purge_last_hid_serv_requests(void);
int rend_client_report_intro_point_failure(extend_info_t *failed_intro,
rend_data_t *rend_data,
unsigned int failure_type);
-int rend_client_rendezvous_acked(origin_circuit_t *circ,
- const uint8_t *request,
- size_t request_len);
int rend_client_receive_rendezvous(origin_circuit_t *circ,
const uint8_t *request,
size_t request_len);
diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c
index e1236bdd0f..458a90058f 100644
--- a/src/or/rendcommon.c
+++ b/src/or/rendcommon.c
@@ -8,6 +8,8 @@
* introducers, services, clients, and rendezvous points.
**/
+#define RENDCOMMON_PRIVATE
+
#include "or.h"
#include "circuitbuild.h"
#include "config.h"
@@ -17,6 +19,7 @@
#include "rendcommon.h"
#include "rendmid.h"
#include "hs_intropoint.h"
+#include "hs_client.h"
#include "rendservice.h"
#include "rephist.h"
#include "router.h"
@@ -395,7 +398,7 @@ rend_encrypt_v2_intro_points_stealth(char **encrypted_out,
/** Attempt to parse the given <b>desc_str</b> and return true if this
* succeeds, false otherwise. */
-static int
+STATIC int
rend_desc_v2_is_parsable(rend_encoded_v2_service_descriptor_t *desc)
{
rend_service_descriptor_t *test_parsed = NULL;
@@ -477,7 +480,10 @@ rend_encode_v2_descriptors(smartlist_t *descs_out,
tor_assert(descriptor_cookie);
}
/* Obtain service_id from public key. */
- crypto_pk_get_digest(service_key, service_id);
+ if (crypto_pk_get_digest(service_key, service_id) < 0) {
+ log_warn(LD_BUG, "Couldn't compute service key digest.");
+ return -1;
+ }
/* Calculate current time-period. */
time_period = get_time_period(now, period, service_id);
/* Determine how many seconds the descriptor will be valid. */
@@ -693,7 +699,7 @@ rend_get_service_id(crypto_pk_t *pk, char *out)
/** Return true iff <b>query</b> is a syntactically valid service ID (as
* generated by rend_get_service_id). */
int
-rend_valid_service_id(const char *query)
+rend_valid_v2_service_id(const char *query)
{
if (strlen(query) != REND_SERVICE_ID_LEN_BASE32)
return 0;
@@ -775,11 +781,11 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint,
break;
case RELAY_COMMAND_INTRODUCE2:
if (origin_circ)
- r = rend_service_receive_introduction(origin_circ,payload,length);
+ r = hs_service_receive_introduce2(origin_circ,payload,length);
break;
case RELAY_COMMAND_INTRODUCE_ACK:
if (origin_circ)
- r = rend_client_introduction_acked(origin_circ,payload,length);
+ r = hs_client_receive_introduce_ack(origin_circ,payload,length);
break;
case RELAY_COMMAND_RENDEZVOUS1:
if (or_circ)
@@ -787,15 +793,15 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint,
break;
case RELAY_COMMAND_RENDEZVOUS2:
if (origin_circ)
- r = rend_client_receive_rendezvous(origin_circ,payload,length);
+ r = hs_client_receive_rendezvous2(origin_circ,payload,length);
break;
case RELAY_COMMAND_INTRO_ESTABLISHED:
if (origin_circ)
- r = rend_service_intro_established(origin_circ,payload,length);
+ r = hs_service_receive_intro_established(origin_circ,payload,length);
break;
case RELAY_COMMAND_RENDEZVOUS_ESTABLISHED:
if (origin_circ)
- r = rend_client_rendezvous_acked(origin_circ,payload,length);
+ r = hs_client_receive_rendezvous_acked(origin_circ,payload,length);
break;
default:
tor_fragile_assert();
@@ -988,7 +994,7 @@ rend_non_anonymous_mode_enabled(const or_options_t *options)
* service.
*/
void
-assert_circ_anonymity_ok(origin_circuit_t *circ,
+assert_circ_anonymity_ok(const origin_circuit_t *circ,
const or_options_t *options)
{
tor_assert(options);
diff --git a/src/or/rendcommon.h b/src/or/rendcommon.h
index 94c2480d86..af8dd60099 100644
--- a/src/or/rendcommon.h
+++ b/src/or/rendcommon.h
@@ -30,7 +30,7 @@ void rend_encoded_v2_service_descriptor_free(
rend_encoded_v2_service_descriptor_t *desc);
void rend_intro_point_free(rend_intro_point_t *intro);
-int rend_valid_service_id(const char *query);
+int rend_valid_v2_service_id(const char *query);
int rend_valid_descriptor_id(const char *query);
int rend_valid_client_name(const char *client_name);
int rend_encode_v2_descriptors(smartlist_t *descs_out,
@@ -60,8 +60,15 @@ int rend_auth_decode_cookie(const char *cookie_in,
int rend_allow_non_anonymous_connection(const or_options_t* options);
int rend_non_anonymous_mode_enabled(const or_options_t *options);
-void assert_circ_anonymity_ok(origin_circuit_t *circ,
+void assert_circ_anonymity_ok(const origin_circuit_t *circ,
const or_options_t *options);
+#ifdef RENDCOMMON_PRIVATE
+
+STATIC int
+rend_desc_v2_is_parsable(rend_encoded_v2_service_descriptor_t *desc);
+
+#endif
+
#endif
diff --git a/src/or/rendmid.c b/src/or/rendmid.c
index 23c3deddaa..66d2f93113 100644
--- a/src/or/rendmid.c
+++ b/src/or/rendmid.c
@@ -71,7 +71,6 @@ rend_mid_establish_intro_legacy(or_circuit_t *circ, const uint8_t *request,
goto err;
}
/* Rest of body: signature of previous data */
- note_crypto_pk_op(REND_MID);
if (crypto_pk_public_checksig_digest(pk,
(char*)request, 2+asn1len+DIGEST_LEN,
(char*)(request+2+DIGEST_LEN+asn1len),
diff --git a/src/or/rendservice.c b/src/or/rendservice.c
index f3b78c4663..99ba9b3d83 100644
--- a/src/or/rendservice.c
+++ b/src/or/rendservice.c
@@ -18,6 +18,7 @@
#include "control.h"
#include "directory.h"
#include "hs_common.h"
+#include "hs_config.h"
#include "main.h"
#include "networkstatus.h"
#include "nodelist.h"
@@ -82,22 +83,6 @@ static smartlist_t* rend_get_service_list_mutable(
smartlist_t* substitute_service_list);
static int rend_max_intro_circs_per_period(unsigned int n_intro_points_wanted);
-/** Represents the mapping from a virtual port of a rendezvous service to
- * a real port on some IP.
- */
-struct rend_service_port_config_s {
- /* The incoming HS virtual port we're mapping */
- uint16_t virtual_port;
- /* Is this an AF_UNIX port? */
- unsigned int is_unix_addr:1;
- /* The outgoing TCP port to use, if !is_unix_addr */
- uint16_t real_port;
- /* The outgoing IPv4 or IPv6 address to use, if !is_unix_addr */
- tor_addr_t real_addr;
- /* The socket path to connect to, if is_unix_addr */
- char unix_addr[FLEXIBLE_ARRAY_MEMBER];
-};
-
/* Hidden service directory file names:
* new file names should be added to rend_service_add_filenames_to_list()
* for sandboxing purposes. */
@@ -163,7 +148,7 @@ rend_service_escaped_dir(const struct rend_service_t *s)
/** Return the number of rendezvous services we have configured. */
int
-num_rend_services(void)
+rend_num_services(void)
{
if (!rend_service_list)
return 0;
@@ -231,18 +216,41 @@ rend_service_free(rend_service_t *service)
tor_free(service);
}
-/** Release all the storage held in rend_service_list.
- */
+/* Release all the storage held in rend_service_staging_list. */
+void
+rend_service_free_staging_list(void)
+{
+ if (rend_service_staging_list) {
+ SMARTLIST_FOREACH(rend_service_staging_list, rend_service_t*, ptr,
+ rend_service_free(ptr));
+ smartlist_free(rend_service_staging_list);
+ rend_service_staging_list = NULL;
+ }
+}
+
+/** Release all the storage held in both rend_service_list and
+ * rend_service_staging_list. */
void
rend_service_free_all(void)
{
- if (!rend_service_list)
- return;
+ if (rend_service_list) {
+ SMARTLIST_FOREACH(rend_service_list, rend_service_t*, ptr,
+ rend_service_free(ptr));
+ smartlist_free(rend_service_list);
+ rend_service_list = NULL;
+ }
+ rend_service_free_staging_list();
+}
+
+/* Initialize the subsystem. */
+void
+rend_service_init(void)
+{
+ tor_assert(!rend_service_list);
+ tor_assert(!rend_service_staging_list);
- SMARTLIST_FOREACH(rend_service_list, rend_service_t*, ptr,
- rend_service_free(ptr));
- smartlist_free(rend_service_list);
- rend_service_list = NULL;
+ rend_service_list = smartlist_new();
+ rend_service_staging_list = smartlist_new();
}
/* Validate a <b>service</b>. Use the <b>service_list</b> to make sure there
@@ -252,8 +260,6 @@ static int
rend_validate_service(const smartlist_t *service_list,
const rend_service_t *service)
{
- int dupe = 0;
-
tor_assert(service_list);
tor_assert(service);
@@ -286,34 +292,6 @@ rend_validate_service(const smartlist_t *service_list,
goto invalid;
}
- /* XXX This duplicate check has two problems:
- *
- * a) It's O(n^2), but the same comment from the bottom of
- * rend_config_services() should apply.
- *
- * b) We only compare directory paths as strings, so we can't
- * detect two distinct paths that specify the same directory
- * (which can arise from symlinks, case-insensitivity, bind
- * mounts, etc.).
- *
- * It also can't detect that two separate Tor instances are trying
- * to use the same HiddenServiceDir; for that, we would need a
- * lock file. But this is enough to detect a simple mistake that
- * at least one person has actually made.
- */
- if (!rend_service_is_ephemeral(service)) {
- /* Skip dupe for ephemeral services. */
- SMARTLIST_FOREACH(service_list, rend_service_t *, ptr,
- dupe = dupe ||
- !strcmp(ptr->directory, service->directory));
- if (dupe) {
- log_warn(LD_REND, "Another hidden service is already configured for "
- "directory %s.",
- rend_service_escaped_dir(service));
- goto invalid;
- }
- }
-
/* Valid. */
return 0;
invalid:
@@ -335,6 +313,7 @@ rend_add_service(smartlist_t *service_list, rend_service_t *service)
/* We must have a service list, even if it's a temporary one, so we can
* check for duplicate services */
if (BUG(!s_list)) {
+ rend_service_free(service);
return -1;
}
@@ -496,41 +475,6 @@ rend_service_port_config_free(rend_service_port_config_t *p)
tor_free(p);
}
-/* Check the directory for <b>service</b>, and add the service to
- * <b>service_list</b>, or to the global list if <b>service_list</b> is NULL.
- * Only add the service to the list if <b>validate_only</b> is false.
- * If <b>validate_only</b> is true, free the service.
- * If <b>service</b> is NULL, ignore it, and return 0.
- * Returns 0 on success, and -1 on failure.
- * Takes ownership of <b>service</b>, either freeing it, or adding it to the
- * global service list.
- */
-STATIC int
-rend_service_check_dir_and_add(smartlist_t *service_list,
- const or_options_t *options,
- rend_service_t *service,
- int validate_only)
-{
- if (!service) {
- /* It is ok for a service to be NULL, this means there are no services */
- return 0;
- }
-
- if (rend_service_check_private_dir(options, service, !validate_only)
- < 0) {
- rend_service_free(service);
- return -1;
- }
-
- smartlist_t *s_list = rend_get_service_list_mutable(service_list);
- /* We must have a service list, even if it's a temporary one, so we can
- * check for duplicate services */
- if (BUG(!s_list)) {
- return -1;
- }
- return rend_add_service(s_list, service);
-}
-
/* Helper: Actual implementation of the pruning on reload which we've
* decoupled in order to make the unit test workeable without ugly hacks.
* Furthermore, this function does NOT free any memory but will nullify the
@@ -657,19 +601,54 @@ rend_service_prune_list(void)
}
}
-/** Set up rend_service_list, based on the values of HiddenServiceDir and
- * HiddenServicePort in <b>options</b>. Return 0 on success and -1 on
- * failure. (If <b>validate_only</b> is set, parse, warn and return as
- * normal, but don't actually change the configured services.)
- */
+/* Copy all the relevant data that the hs_service object contains over to the
+ * rend_service_t object. The reason to do so is because when configuring a
+ * service, we go through a generic handler that creates an hs_service_t
+ * object which so we have to copy the parsed values to a rend service object
+ * which is version 2 specific. */
+static void
+service_config_shadow_copy(rend_service_t *service,
+ hs_service_config_t *config)
+{
+ tor_assert(service);
+ tor_assert(config);
+
+ service->directory = tor_strdup(config->directory_path);
+ service->dir_group_readable = config->dir_group_readable;
+ service->allow_unknown_ports = config->allow_unknown_ports;
+ /* This value can't go above HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT (65535)
+ * if the code flow is right so this cast is safe. But just in case, we'll
+ * check it. */
+ service->max_streams_per_circuit = (int) config->max_streams_per_rdv_circuit;
+ if (BUG(config->max_streams_per_rdv_circuit >
+ HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT)) {
+ service->max_streams_per_circuit = HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT;
+ }
+ service->max_streams_close_circuit = config->max_streams_close_circuit;
+ service->n_intro_points_wanted = config->num_intro_points;
+ /* Switching ownership of the ports to the rend service object. */
+ smartlist_add_all(service->ports, config->ports);
+ smartlist_free(config->ports);
+ config->ports = NULL;
+}
+
+/* Parse the hidden service configuration starting at <b>line_</b> using the
+ * already configured generic service configuration in <b>config</b>. This
+ * function will translate the config object to a rend_service_t and add it to
+ * the temporary list if valid. If <b>validate_only</b> is set, parse, warn
+ * and return as normal but don't actually add the service to the list. */
int
-rend_config_services(const or_options_t *options, int validate_only)
+rend_config_service(const config_line_t *line_,
+ const or_options_t *options,
+ hs_service_config_t *config)
{
- config_line_t *line;
+ const config_line_t *line;
rend_service_t *service = NULL;
- rend_service_port_config_t *portcfg;
- int ok = 0;
- int rv = -1;
+
+ /* line_ can be NULL which would mean that the service configuration only
+ * have one line that is the directory directive. */
+ tor_assert(options);
+ tor_assert(config);
/* Use the staging service list so that we can check then do the pruning
* process using the main list at the end. */
@@ -677,100 +656,23 @@ rend_config_services(const or_options_t *options, int validate_only)
rend_service_staging_list = smartlist_new();
}
- for (line = options->RendConfigLines; line; line = line->next) {
+ /* Initialize service. */
+ service = tor_malloc_zero(sizeof(rend_service_t));
+ service->intro_period_started = time(NULL);
+ service->ports = smartlist_new();
+ /* From the hs_service object which has been used to load the generic
+ * options, we'll copy over the useful data to the rend_service_t object. */
+ service_config_shadow_copy(service, config);
+
+ for (line = line_; line; line = line->next) {
if (!strcasecmp(line->key, "HiddenServiceDir")) {
- if (service) {
- /* Validate and register the service we just finished parsing this
- * code registers every service except the last one parsed, which is
- * validated and registered below the loop. */
- if (rend_validate_service(rend_service_staging_list, service) < 0) {
- goto free_and_return;
- }
- if (rend_service_check_dir_and_add(rend_service_staging_list, options,
- service, validate_only) < 0) {
- /* The above frees the service on error so nullify the pointer. */
- service = NULL;
- goto free_and_return;
- }
- }
- service = tor_malloc_zero(sizeof(rend_service_t));
- service->directory = tor_strdup(line->value);
- service->ports = smartlist_new();
- service->intro_period_started = time(NULL);
- service->n_intro_points_wanted = NUM_INTRO_POINTS_DEFAULT;
- continue;
- }
- if (!service) {
- log_warn(LD_CONFIG, "%s with no preceding HiddenServiceDir directive",
- line->key);
- goto free_and_return;
+ /* We just hit the next hidden service, stop right now. */
+ break;
}
- if (!strcasecmp(line->key, "HiddenServicePort")) {
- char *err_msg = NULL;
- portcfg = rend_service_parse_port_config(line->value, " ", &err_msg);
- if (!portcfg) {
- if (err_msg)
- log_warn(LD_CONFIG, "%s", err_msg);
- tor_free(err_msg);
- goto free_and_return;
- }
- tor_assert(!err_msg);
- smartlist_add(service->ports, portcfg);
- } else if (!strcasecmp(line->key, "HiddenServiceAllowUnknownPorts")) {
- service->allow_unknown_ports = (int)tor_parse_long(line->value,
- 10, 0, 1, &ok, NULL);
- if (!ok) {
- log_warn(LD_CONFIG,
- "HiddenServiceAllowUnknownPorts should be 0 or 1, not %s",
- line->value);
- goto free_and_return;
- }
- log_info(LD_CONFIG,
- "HiddenServiceAllowUnknownPorts=%d for %s",
- (int)service->allow_unknown_ports,
- rend_service_escaped_dir(service));
- } else if (!strcasecmp(line->key,
- "HiddenServiceDirGroupReadable")) {
- service->dir_group_readable = (int)tor_parse_long(line->value,
- 10, 0, 1, &ok, NULL);
- if (!ok) {
- log_warn(LD_CONFIG,
- "HiddenServiceDirGroupReadable should be 0 or 1, not %s",
- line->value);
- goto free_and_return;
- }
- log_info(LD_CONFIG,
- "HiddenServiceDirGroupReadable=%d for %s",
- service->dir_group_readable,
- rend_service_escaped_dir(service));
- } else if (!strcasecmp(line->key, "HiddenServiceMaxStreams")) {
- service->max_streams_per_circuit = (int)tor_parse_long(line->value,
- 10, 0, 65535, &ok, NULL);
- if (!ok) {
- log_warn(LD_CONFIG,
- "HiddenServiceMaxStreams should be between 0 and %d, not %s",
- 65535, line->value);
- goto free_and_return;
- }
- log_info(LD_CONFIG,
- "HiddenServiceMaxStreams=%d for %s",
- service->max_streams_per_circuit,
- rend_service_escaped_dir(service));
- } else if (!strcasecmp(line->key, "HiddenServiceMaxStreamsCloseCircuit")) {
- service->max_streams_close_circuit = (int)tor_parse_long(line->value,
- 10, 0, 1, &ok, NULL);
- if (!ok) {
- log_warn(LD_CONFIG,
- "HiddenServiceMaxStreamsCloseCircuit should be 0 or 1, "
- "not %s",
- line->value);
- goto free_and_return;
- }
- log_info(LD_CONFIG,
- "HiddenServiceMaxStreamsCloseCircuit=%d for %s",
- (int)service->max_streams_close_circuit,
- rend_service_escaped_dir(service));
- } else if (!strcasecmp(line->key, "HiddenServiceNumIntroductionPoints")) {
+ /* Number of introduction points. */
+ if (!strcasecmp(line->key, "HiddenServiceNumIntroductionPoints")) {
+ int ok = 0;
+ /* Those are specific defaults for version 2. */
service->n_intro_points_wanted =
(unsigned int) tor_parse_long(line->value, 10,
0, NUM_INTRO_POINTS_MAX, &ok, NULL);
@@ -779,12 +681,13 @@ rend_config_services(const or_options_t *options, int validate_only)
"HiddenServiceNumIntroductionPoints "
"should be between %d and %d, not %s",
0, NUM_INTRO_POINTS_MAX, line->value);
- goto free_and_return;
+ goto err;
}
log_info(LD_CONFIG, "HiddenServiceNumIntroductionPoints=%d for %s",
- service->n_intro_points_wanted,
- rend_service_escaped_dir(service));
- } else if (!strcasecmp(line->key, "HiddenServiceAuthorizeClient")) {
+ service->n_intro_points_wanted, escaped(service->directory));
+ continue;
+ }
+ if (!strcasecmp(line->key, "HiddenServiceAuthorizeClient")) {
/* Parse auth type and comma-separated list of client names and add a
* rend_authorized_client_t for each client to the service's list
* of authorized clients. */
@@ -794,7 +697,7 @@ rend_config_services(const or_options_t *options, int validate_only)
if (service->auth_type != REND_NO_AUTH) {
log_warn(LD_CONFIG, "Got multiple HiddenServiceAuthorizeClient "
"lines for a single service.");
- goto free_and_return;
+ goto err;
}
type_names_split = smartlist_new();
smartlist_split_string(type_names_split, line->value, " ", 0, 2);
@@ -802,7 +705,8 @@ rend_config_services(const or_options_t *options, int validate_only)
log_warn(LD_BUG, "HiddenServiceAuthorizeClient has no value. This "
"should have been prevented when parsing the "
"configuration.");
- goto free_and_return;
+ smartlist_free(type_names_split);
+ goto err;
}
authname = smartlist_get(type_names_split, 0);
if (!strcasecmp(authname, "basic")) {
@@ -816,7 +720,7 @@ rend_config_services(const or_options_t *options, int validate_only)
(char *) smartlist_get(type_names_split, 0));
SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp));
smartlist_free(type_names_split);
- goto free_and_return;
+ goto err;
}
service->clients = smartlist_new();
if (smartlist_len(type_names_split) < 2) {
@@ -853,7 +757,7 @@ rend_config_services(const or_options_t *options, int validate_only)
client_name, REND_CLIENTNAME_MAX_LEN);
SMARTLIST_FOREACH(clients, char *, cp, tor_free(cp));
smartlist_free(clients);
- goto free_and_return;
+ goto err;
}
client = tor_malloc_zero(sizeof(rend_authorized_client_t));
client->client_name = tor_strdup(client_name);
@@ -875,56 +779,29 @@ rend_config_services(const or_options_t *options, int validate_only)
smartlist_len(service->clients),
service->auth_type == REND_BASIC_AUTH ? 512 : 16,
service->auth_type == REND_BASIC_AUTH ? "basic" : "stealth");
- goto free_and_return;
- }
- } else {
- tor_assert(!strcasecmp(line->key, "HiddenServiceVersion"));
- if (strcmp(line->value, "2")) {
- log_warn(LD_CONFIG,
- "The only supported HiddenServiceVersion is 2.");
- goto free_and_return;
+ goto err;
}
+ continue;
}
}
- /* Validate the last service that we just parsed. */
- if (service &&
- rend_validate_service(rend_service_staging_list, service) < 0) {
- goto free_and_return;
- }
- /* register the final service after we have finished parsing all services
- * this code only registers the last service, other services are registered
- * within the loop. It is ok for this service to be NULL, it is ignored. */
- if (rend_service_check_dir_and_add(rend_service_staging_list, options,
- service, validate_only) < 0) {
- /* Service object is freed on error so nullify pointer. */
- service = NULL;
- goto free_and_return;
+ /* Validate the service just parsed. */
+ if (rend_validate_service(rend_service_staging_list, service) < 0) {
+ /* Service is in the staging list so don't try to free it. */
+ goto err;
}
- /* The service is in the staging list so nullify pointer to avoid double
- * free of this object in case of error because we lost ownership of it at
- * this point. */
- service = NULL;
- /* Free the newly added services if validating */
- if (validate_only) {
- rv = 0;
- goto free_and_return;
+ /* Add it to the temporary list which we will use to prune our current
+ * list if any after configuring all services. */
+ if (rend_add_service(rend_service_staging_list, service) < 0) {
+ /* The object has been freed on error already. */
+ service = NULL;
+ goto err;
}
- /* This could be a reload of configuration so try to prune the main list
- * using the staging one. And we know we are not in validate mode here.
- * After this, the main and staging list will point to the right place and
- * be in a quiescent usable state. */
- rend_service_prune_list();
-
return 0;
- free_and_return:
+ err:
rend_service_free(service);
- SMARTLIST_FOREACH(rend_service_staging_list, rend_service_t *, ptr,
- rend_service_free(ptr));
- smartlist_free(rend_service_staging_list);
- rend_service_staging_list = NULL;
- return rv;
+ return -1;
}
/** Add the ephemeral service <b>pk</b>/<b>ports</b> if possible, using
@@ -1013,7 +890,7 @@ int
rend_service_del_ephemeral(const char *service_id)
{
rend_service_t *s;
- if (!rend_valid_service_id(service_id)) {
+ if (!rend_valid_v2_service_id(service_id)) {
log_warn(LD_CONFIG, "Requested malformed Onion Service id for removal.");
return -1;
}
@@ -1170,15 +1047,8 @@ rend_service_update_descriptor(rend_service_t *service)
static char *
rend_service_path(const rend_service_t *service, const char *file_name)
{
- char *file_path = NULL;
-
tor_assert(service->directory);
-
- /* Can never fail: asserts rather than leaving file_path NULL. */
- tor_asprintf(&file_path, "%s%s%s",
- service->directory, PATH_SEPARATOR, file_name);
-
- return file_path;
+ return hs_path_from_filename(service->directory, file_name);
}
/* Allocate and return a string containing the path to the single onion
@@ -1548,9 +1418,9 @@ rend_service_load_keys(rend_service_t *s)
char *fname = NULL;
char buf[128];
- /* Make sure the directory was created and single onion poisoning was
- * checked before calling this function */
- if (BUG(rend_service_check_private_dir(get_options(), s, 0) < 0))
+ /* Create the directory if needed which will also poison it in case of
+ * single onion service. */
+ if (rend_service_check_private_dir(get_options(), s, 1) < 0)
goto err;
/* Load key */
@@ -1808,24 +1678,6 @@ rend_service_get_by_service_id(const char *id)
return NULL;
}
-/** Return 1 if any virtual port in <b>service</b> wants a circuit
- * to have good uptime. Else return 0.
- */
-static int
-rend_service_requires_uptime(rend_service_t *service)
-{
- int i;
- rend_service_port_config_t *p;
-
- for (i=0; i < smartlist_len(service->ports); ++i) {
- p = smartlist_get(service->ports, i);
- if (smartlist_contains_int_as_string(get_options()->LongLivedPorts,
- p->virtual_port))
- return 1;
- }
- return 0;
-}
-
/** Check client authorization of a given <b>descriptor_cookie</b> of
* length <b>cookie_len</b> for <b>service</b>. Return 1 for success
* and 0 for failure. */
@@ -2143,7 +1995,7 @@ rend_service_receive_introduction(origin_circuit_t *circuit,
goto err;
}
- circ_needs_uptime = rend_service_requires_uptime(service);
+ circ_needs_uptime = hs_service_requires_uptime_circ(service->ports);
/* help predict this next time */
rep_hist_note_used_internal(now, circ_needs_uptime, 1);
@@ -2195,7 +2047,9 @@ rend_service_receive_introduction(origin_circuit_t *circuit,
cpath->rend_dh_handshake_state = dh;
dh = NULL;
- if (circuit_init_cpath_crypto(cpath,keys+DIGEST_LEN,1)<0)
+ if (circuit_init_cpath_crypto(cpath,
+ keys+DIGEST_LEN, sizeof(keys)-DIGEST_LEN,
+ 1, 0)<0)
goto err;
memcpy(cpath->rend_circ_nonce, keys, DIGEST_LEN);
@@ -2812,7 +2666,14 @@ rend_service_decrypt_intro(
/* Check that this cell actually matches this service key */
/* first DIGEST_LEN bytes of request is intro or service pk digest */
- crypto_pk_get_digest(key, (char *)key_digest);
+ if (crypto_pk_get_digest(key, (char *)key_digest) < 0) {
+ if (err_msg_out)
+ *err_msg_out = tor_strdup("Couldn't compute RSA digest.");
+ log_warn(LD_BUG, "Couldn't compute key digest.");
+ status = -7;
+ goto err;
+ }
+
if (tor_memneq(key_digest, intro->pk, DIGEST_LEN)) {
if (err_msg_out) {
base32_encode(service_id, REND_SERVICE_ID_LEN_BASE32 + 1,
@@ -2843,10 +2704,8 @@ rend_service_decrypt_intro(
}
/* Decrypt the encrypted part */
-
- note_crypto_pk_op(REND_SERVER);
result =
- crypto_pk_private_hybrid_decrypt(
+ crypto_pk_obsolete_private_hybrid_decrypt(
key, (char *)buf, sizeof(buf),
(const char *)(intro->ciphertext), intro->ciphertext_len,
PK_PKCS1_OAEP_PADDING, 1);
@@ -3040,29 +2899,6 @@ rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc)
tor_assert(oldcirc->base_.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
- /* Don't relaunch the same rend circ twice. */
- if (oldcirc->hs_service_side_rend_circ_has_been_relaunched) {
- log_info(LD_REND, "Rendezvous circuit to %s has already been relaunched; "
- "not relaunching it again.",
- oldcirc->build_state ?
- safe_str(extend_info_describe(oldcirc->build_state->chosen_exit))
- : "*unknown*");
- return;
- }
- oldcirc->hs_service_side_rend_circ_has_been_relaunched = 1;
-
- if (!oldcirc->build_state ||
- oldcirc->build_state->failure_count > MAX_REND_FAILURES ||
- oldcirc->build_state->expiry_time < time(NULL)) {
- log_info(LD_REND,
- "Attempt to build circuit to %s for rendezvous has failed "
- "too many times or expired; giving up.",
- oldcirc->build_state ?
- safe_str(extend_info_describe(oldcirc->build_state->chosen_exit))
- : "*unknown*");
- return;
- }
-
oldstate = oldcirc->build_state;
tor_assert(oldstate);
@@ -3230,10 +3066,11 @@ count_intro_point_circuits(const rend_service_t *service)
crypto material. On success, fill <b>cell_body_out</b> and return the number
of bytes written. On fail, return -1.
*/
-STATIC ssize_t
-encode_establish_intro_cell_legacy(char *cell_body_out,
- size_t cell_body_out_len,
- crypto_pk_t *intro_key, char *rend_circ_nonce)
+ssize_t
+rend_service_encode_establish_intro_cell(char *cell_body_out,
+ size_t cell_body_out_len,
+ crypto_pk_t *intro_key,
+ const char *rend_circ_nonce)
{
int retval = -1;
int r;
@@ -3258,7 +3095,6 @@ encode_establish_intro_cell_legacy(char *cell_body_out,
if (crypto_digest(cell_body_out+len, auth, DIGEST_LEN+9))
goto err;
len += 20;
- note_crypto_pk_op(REND_SERVER);
r = crypto_pk_private_sign_digest(intro_key, cell_body_out+len,
cell_body_out_len - len,
cell_body_out, len);
@@ -3371,7 +3207,7 @@ rend_service_intro_has_opened(origin_circuit_t *circuit)
/* Send the ESTABLISH_INTRO cell */
{
ssize_t len;
- len = encode_establish_intro_cell_legacy(buf, sizeof(buf),
+ len = rend_service_encode_establish_intro_cell(buf, sizeof(buf),
circuit->intro_key,
circuit->cpath->prev->rend_circ_nonce);
if (len < 0) {
@@ -4098,10 +3934,9 @@ rend_max_intro_circs_per_period(unsigned int n_intro_points_wanted)
* This is called once a second by the main loop.
*/
void
-rend_consider_services_intro_points(void)
+rend_consider_services_intro_points(time_t now)
{
int i;
- time_t now;
const or_options_t *options = get_options();
/* Are we in single onion mode? */
const int allow_direct = rend_service_allow_non_anonymous_connection(
@@ -4118,7 +3953,6 @@ rend_consider_services_intro_points(void)
exclude_nodes = smartlist_new();
retry_nodes = smartlist_new();
- now = time(NULL);
SMARTLIST_FOREACH_BEGIN(rend_service_list, rend_service_t *, service) {
int r;
@@ -4396,60 +4230,6 @@ rend_service_dump_stats(int severity)
}
}
-#ifdef HAVE_SYS_UN_H
-
-/** Given <b>ports</b>, a smarlist containing rend_service_port_config_t,
- * add the given <b>p</b>, a AF_UNIX port to the list. Return 0 on success
- * else return -ENOSYS if AF_UNIX is not supported (see function in the
- * #else statement below). */
-static int
-add_unix_port(smartlist_t *ports, rend_service_port_config_t *p)
-{
- tor_assert(ports);
- tor_assert(p);
- tor_assert(p->is_unix_addr);
-
- smartlist_add(ports, p);
- return 0;
-}
-
-/** Given <b>conn</b> set it to use the given port <b>p</b> values. Return 0
- * on success else return -ENOSYS if AF_UNIX is not supported (see function
- * in the #else statement below). */
-static int
-set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p)
-{
- tor_assert(conn);
- tor_assert(p);
- tor_assert(p->is_unix_addr);
-
- conn->base_.socket_family = AF_UNIX;
- tor_addr_make_unspec(&conn->base_.addr);
- conn->base_.port = 1;
- conn->base_.address = tor_strdup(p->unix_addr);
- return 0;
-}
-
-#else /* defined(HAVE_SYS_UN_H) */
-
-static int
-set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p)
-{
- (void) conn;
- (void) p;
- return -ENOSYS;
-}
-
-static int
-add_unix_port(smartlist_t *ports, rend_service_port_config_t *p)
-{
- (void) ports;
- (void) p;
- return -ENOSYS;
-}
-
-#endif /* HAVE_SYS_UN_H */
-
/** Given <b>conn</b>, a rendezvous exit stream, look up the hidden service for
* 'circ', and look up the port and address based on conn-\>port.
* Assign the actual conn-\>addr and conn-\>port. Return -2 on failure
@@ -4462,9 +4242,6 @@ rend_service_set_connection_addr_port(edge_connection_t *conn,
{
rend_service_t *service;
char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
- smartlist_t *matching_ports;
- rend_service_port_config_t *chosen_port;
- unsigned int warn_once = 0;
const char *rend_pk_digest;
tor_assert(circ->base_.purpose == CIRCUIT_PURPOSE_S_REND_JOINED);
@@ -4500,41 +4277,9 @@ rend_service_set_connection_addr_port(edge_connection_t *conn,
return service->max_streams_close_circuit ? -2 : -1;
}
}
- matching_ports = smartlist_new();
- SMARTLIST_FOREACH(service->ports, rend_service_port_config_t *, p,
- {
- if (conn->base_.port != p->virtual_port) {
- continue;
- }
- if (!(p->is_unix_addr)) {
- smartlist_add(matching_ports, p);
- } else {
- if (add_unix_port(matching_ports, p)) {
- if (!warn_once) {
- /* Unix port not supported so warn only once. */
- log_warn(LD_REND,
- "Saw AF_UNIX virtual port mapping for port %d on service "
- "%s, which is unsupported on this platform. Ignoring it.",
- conn->base_.port, serviceid);
- }
- warn_once++;
- }
- }
- });
- chosen_port = smartlist_choose(matching_ports);
- smartlist_free(matching_ports);
- if (chosen_port) {
- if (!(chosen_port->is_unix_addr)) {
- /* Get a non-AF_UNIX connection ready for connection_exit_connect() */
- tor_addr_copy(&conn->base_.addr, &chosen_port->real_addr);
- conn->base_.port = chosen_port->real_port;
- } else {
- if (set_unix_port(conn, chosen_port)) {
- /* Simply impossible to end up here else we were able to add a Unix
- * port without AF_UNIX support... ? */
- tor_assert(0);
- }
- }
+
+ if (hs_set_conn_addr_port(service->ports, conn) == 0) {
+ /* Successfully set the port to the connection. We are done. */
return 0;
}
diff --git a/src/or/rendservice.h b/src/or/rendservice.h
index 1583a6010b..ed1044f04a 100644
--- a/src/or/rendservice.h
+++ b/src/or/rendservice.h
@@ -13,11 +13,9 @@
#define TOR_RENDSERVICE_H
#include "or.h"
+#include "hs_service.h"
typedef struct rend_intro_cell_s rend_intro_cell_t;
-typedef struct rend_service_port_config_s rend_service_port_config_t;
-
-#ifdef RENDSERVICE_PRIVATE
/* This can be used for both INTRODUCE1 and INTRODUCE2 */
@@ -63,6 +61,8 @@ struct rend_intro_cell_s {
uint8_t dh[DH_KEY_LEN];
};
+#ifdef RENDSERVICE_PRIVATE
+
/** Represents a single hidden service running at this OP. */
typedef struct rend_service_t {
/* Fields specified in config file */
@@ -119,20 +119,12 @@ typedef struct rend_service_t {
STATIC void rend_service_free(rend_service_t *service);
STATIC char *rend_service_sos_poison_path(const rend_service_t *service);
-STATIC int rend_service_check_dir_and_add(smartlist_t *service_list,
- const or_options_t *options,
- rend_service_t *service,
- int validate_only);
STATIC int rend_service_verify_single_onion_poison(
const rend_service_t *s,
const or_options_t *options);
STATIC int rend_service_poison_new_single_onion_dir(
const rend_service_t *s,
const or_options_t* options);
-STATIC ssize_t encode_establish_intro_cell_legacy(char *cell_body_out,
- size_t cell_body_out_len,
- crypto_pk_t *intro_key,
- char *rend_circ_nonce);
#ifdef TOR_UNIT_TESTS
STATIC void set_rend_service_list(smartlist_t *new_list);
@@ -143,13 +135,16 @@ STATIC void rend_service_prune_list_impl_(void);
#endif /* RENDSERVICE_PRIVATE */
-int num_rend_services(void);
-int rend_config_services(const or_options_t *options, int validate_only);
+int rend_num_services(void);
+int rend_config_service(const config_line_t *line_,
+ const or_options_t *options,
+ hs_service_config_t *config);
void rend_service_prune_list(void);
+void rend_service_free_staging_list(void);
int rend_service_load_all_keys(const smartlist_t *service_list);
void rend_services_add_filenames_to_lists(smartlist_t *open_lst,
smartlist_t *stat_lst);
-void rend_consider_services_intro_points(void);
+void rend_consider_services_intro_points(time_t now);
void rend_consider_services_upload(time_t now);
void rend_hsdir_routers_changed(void);
void rend_consider_descriptor_republication(void);
@@ -172,6 +167,10 @@ rend_intro_cell_t * rend_service_begin_parse_intro(const uint8_t *request,
char **err_msg_out);
int rend_service_parse_intro_plaintext(rend_intro_cell_t *intro,
char **err_msg_out);
+ssize_t rend_service_encode_establish_intro_cell(char *cell_body_out,
+ size_t cell_body_out_len,
+ crypto_pk_t *intro_key,
+ const char *rend_circ_nonce);
int rend_service_validate_intro_late(const rend_intro_cell_t *intro,
char **err_msg_out);
void rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc);
@@ -179,6 +178,7 @@ int rend_service_set_connection_addr_port(edge_connection_t *conn,
origin_circuit_t *circ);
void rend_service_dump_stats(int severity);
void rend_service_free_all(void);
+void rend_service_init(void);
rend_service_port_config_t *rend_service_parse_port_config(const char *string,
const char *sep,
diff --git a/src/or/rephist.c b/src/or/rephist.c
index 72a5cc5a9b..ae45c5023a 100644
--- a/src/or/rephist.c
+++ b/src/or/rephist.c
@@ -2064,105 +2064,6 @@ rep_hist_circbuilding_dormant(time_t now)
return 1;
}
-/** Structure to track how many times we've done each public key operation. */
-static struct {
- /** How many directory objects have we signed? */
- unsigned long n_signed_dir_objs;
- /** How many routerdescs have we signed? */
- unsigned long n_signed_routerdescs;
- /** How many directory objects have we verified? */
- unsigned long n_verified_dir_objs;
- /** How many routerdescs have we verified */
- unsigned long n_verified_routerdescs;
- /** How many onionskins have we encrypted to build circuits? */
- unsigned long n_onionskins_encrypted;
- /** How many onionskins have we decrypted to do circuit build requests? */
- unsigned long n_onionskins_decrypted;
- /** How many times have we done the TLS handshake as a client? */
- unsigned long n_tls_client_handshakes;
- /** How many times have we done the TLS handshake as a server? */
- unsigned long n_tls_server_handshakes;
- /** How many PK operations have we done as a hidden service client? */
- unsigned long n_rend_client_ops;
- /** How many PK operations have we done as a hidden service midpoint? */
- unsigned long n_rend_mid_ops;
- /** How many PK operations have we done as a hidden service provider? */
- unsigned long n_rend_server_ops;
-} pk_op_counts = {0,0,0,0,0,0,0,0,0,0,0};
-
-/** Increment the count of the number of times we've done <b>operation</b>. */
-void
-note_crypto_pk_op(pk_op_t operation)
-{
- switch (operation)
- {
- case SIGN_DIR:
- pk_op_counts.n_signed_dir_objs++;
- break;
- case SIGN_RTR:
- pk_op_counts.n_signed_routerdescs++;
- break;
- case VERIFY_DIR:
- pk_op_counts.n_verified_dir_objs++;
- break;
- case VERIFY_RTR:
- pk_op_counts.n_verified_routerdescs++;
- break;
- case ENC_ONIONSKIN:
- pk_op_counts.n_onionskins_encrypted++;
- break;
- case DEC_ONIONSKIN:
- pk_op_counts.n_onionskins_decrypted++;
- break;
- case TLS_HANDSHAKE_C:
- pk_op_counts.n_tls_client_handshakes++;
- break;
- case TLS_HANDSHAKE_S:
- pk_op_counts.n_tls_server_handshakes++;
- break;
- case REND_CLIENT:
- pk_op_counts.n_rend_client_ops++;
- break;
- case REND_MID:
- pk_op_counts.n_rend_mid_ops++;
- break;
- case REND_SERVER:
- pk_op_counts.n_rend_server_ops++;
- break;
- default:
- log_warn(LD_BUG, "Unknown pk operation %d", operation);
- }
-}
-
-/** Log the number of times we've done each public/private-key operation. */
-void
-dump_pk_ops(int severity)
-{
- tor_log(severity, LD_HIST,
- "PK operations: %lu directory objects signed, "
- "%lu directory objects verified, "
- "%lu routerdescs signed, "
- "%lu routerdescs verified, "
- "%lu onionskins encrypted, "
- "%lu onionskins decrypted, "
- "%lu client-side TLS handshakes, "
- "%lu server-side TLS handshakes, "
- "%lu rendezvous client operations, "
- "%lu rendezvous middle operations, "
- "%lu rendezvous server operations.",
- pk_op_counts.n_signed_dir_objs,
- pk_op_counts.n_verified_dir_objs,
- pk_op_counts.n_signed_routerdescs,
- pk_op_counts.n_verified_routerdescs,
- pk_op_counts.n_onionskins_encrypted,
- pk_op_counts.n_onionskins_decrypted,
- pk_op_counts.n_tls_client_handshakes,
- pk_op_counts.n_tls_server_handshakes,
- pk_op_counts.n_rend_client_ops,
- pk_op_counts.n_rend_mid_ops,
- pk_op_counts.n_rend_server_ops);
-}
-
/*** Exit port statistics ***/
/* Some constants */
@@ -2651,7 +2552,7 @@ rep_hist_format_buffer_stats(time_t now)
processed_cells_string,
queued_cells_string,
time_in_queue_string,
- (number_of_circuits + SHARES - 1) / SHARES);
+ CEIL_DIV(number_of_circuits, SHARES));
tor_free(processed_cells_string);
tor_free(queued_cells_string);
tor_free(time_in_queue_string);
diff --git a/src/or/rephist.h b/src/or/rephist.h
index 2b1c2e7ec7..8f6d46616d 100644
--- a/src/or/rephist.h
+++ b/src/or/rephist.h
@@ -62,9 +62,6 @@ int any_predicted_circuits(time_t now);
int rep_hist_circbuilding_dormant(time_t now);
int predicted_ports_prediction_time_remaining(time_t now);
-void note_crypto_pk_op(pk_op_t operation);
-void dump_pk_ops(int severity);
-
void rep_hist_exit_stats_init(time_t now);
void rep_hist_reset_exit_stats(time_t now);
void rep_hist_exit_stats_term(void);
diff --git a/src/or/router.c b/src/or/router.c
index 2187a76b48..c8e78659c0 100644
--- a/src/or/router.c
+++ b/src/or/router.c
@@ -257,7 +257,11 @@ set_server_identity_key(crypto_pk_t *k)
{
crypto_pk_free(server_identitykey);
server_identitykey = k;
- crypto_pk_get_digest(server_identitykey, server_identitykey_digest);
+ if (crypto_pk_get_digest(server_identitykey,
+ server_identitykey_digest) < 0) {
+ log_err(LD_BUG, "Couldn't compute our own identity key digest.");
+ tor_assert(0);
+ }
}
/** Make sure that we have set up our identity keys to match or not match as
@@ -956,8 +960,12 @@ init_keys(void)
}
cert = get_my_v3_authority_cert();
if (cert) {
- crypto_pk_get_digest(get_my_v3_authority_cert()->identity_key,
- v3_digest);
+ if (crypto_pk_get_digest(get_my_v3_authority_cert()->identity_key,
+ v3_digest) < 0) {
+ log_err(LD_BUG, "Couldn't compute my v3 authority identity key "
+ "digest.");
+ return -1;
+ }
v3_digest_set = 1;
}
}
@@ -1065,7 +1073,7 @@ init_keys(void)
/* 4. Build our router descriptor. */
/* Must be called after keys are initialized. */
mydesc = router_get_my_descriptor();
- if (authdir_mode_handles_descs(options, ROUTER_PURPOSE_GENERAL)) {
+ if (authdir_mode_v3(options)) {
const char *m = NULL;
routerinfo_t *ri;
/* We need to add our own fingerprint so it gets recognized. */
@@ -1596,32 +1604,19 @@ authdir_mode_v3(const or_options_t *options)
{
return authdir_mode(options) && options->V3AuthoritativeDir != 0;
}
-/** Return true iff we are a v3 directory authority. */
-int
-authdir_mode_any_main(const or_options_t *options)
-{
- return options->V3AuthoritativeDir;
-}
-/** Return true if we believe ourselves to be any kind of
- * authoritative directory beyond just a hidserv authority. */
-int
-authdir_mode_any_nonhidserv(const or_options_t *options)
-{
- return options->BridgeAuthoritativeDir ||
- authdir_mode_any_main(options);
-}
/** Return true iff we are an authoritative directory server that is
* authoritative about receiving and serving descriptors of type
- * <b>purpose</b> on its dirport. Use -1 for "any purpose". */
+ * <b>purpose</b> on its dirport.
+ */
int
authdir_mode_handles_descs(const or_options_t *options, int purpose)
{
- if (purpose < 0)
- return authdir_mode_any_nonhidserv(options);
+ if (BUG(purpose < 0)) /* Deprecated. */
+ return authdir_mode(options);
else if (purpose == ROUTER_PURPOSE_GENERAL)
- return authdir_mode_any_main(options);
+ return authdir_mode_v3(options);
else if (purpose == ROUTER_PURPOSE_BRIDGE)
- return (options->BridgeAuthoritativeDir);
+ return authdir_mode_bridge(options);
else
return 0;
}
@@ -1633,7 +1628,7 @@ authdir_mode_publishes_statuses(const or_options_t *options)
{
if (authdir_mode_bridge(options))
return 0;
- return authdir_mode_any_nonhidserv(options);
+ return authdir_mode(options);
}
/** Return true iff we are an authoritative directory server that
* tests reachability of the descriptors it learns about.
@@ -1641,7 +1636,7 @@ authdir_mode_publishes_statuses(const or_options_t *options)
int
authdir_mode_tests_reachability(const or_options_t *options)
{
- return authdir_mode_handles_descs(options, -1);
+ return authdir_mode(options);
}
/** Return true iff we believe ourselves to be a bridge authoritative
* directory server.
@@ -1869,7 +1864,7 @@ static const char *desc_gen_reason = NULL;
* now. */
static time_t desc_clean_since = 0;
/** Why did we mark the descriptor dirty? */
-static const char *desc_dirty_reason = NULL;
+static const char *desc_dirty_reason = "Tor just started";
/** Boolean: do we need to regenerate the above? */
static int desc_needs_upload = 0;
@@ -3022,7 +3017,6 @@ router_dump_router_to_string(routerinfo_t *router,
crypto_digest_smartlist(digest, DIGEST_LEN, chunks, "", DIGEST_SHA1);
- note_crypto_pk_op(SIGN_RTR);
{
char *sig;
if (!(sig = router_get_dirobj_signature(digest, DIGEST_LEN, ident_key))) {
diff --git a/src/or/router.h b/src/or/router.h
index 9c5def5218..97f331713a 100644
--- a/src/or/router.h
+++ b/src/or/router.h
@@ -54,8 +54,6 @@ int net_is_disabled(void);
int authdir_mode(const or_options_t *options);
int authdir_mode_v3(const or_options_t *options);
-int authdir_mode_any_main(const or_options_t *options);
-int authdir_mode_any_nonhidserv(const or_options_t *options);
int authdir_mode_handles_descs(const or_options_t *options, int purpose);
int authdir_mode_publishes_statuses(const or_options_t *options);
int authdir_mode_tests_reachability(const or_options_t *options);
diff --git a/src/or/routerkeys.c b/src/or/routerkeys.c
index 71889d2721..4822ff3be3 100644
--- a/src/or/routerkeys.c
+++ b/src/or/routerkeys.c
@@ -1136,6 +1136,108 @@ init_mock_ed_keys(const crypto_pk_t *rsa_identity_key)
#undef MAKECERT
#endif
+/**
+ * Print the ISO8601-formated <b>expiration</b> for a certificate with
+ * some <b>description</b> to stdout.
+ *
+ * For example, for a signing certificate, this might print out:
+ * signing-cert-expiry: 2017-07-25 08:30:15 UTC
+ */
+static void
+print_cert_expiration(const char *expiration,
+ const char *description)
+{
+ fprintf(stderr, "%s-cert-expiry: %s\n", description, expiration);
+}
+
+/**
+ * Log when a certificate, <b>cert</b>, with some <b>description</b> and
+ * stored in a file named <b>fname</b>, is going to expire.
+ */
+static void
+log_ed_cert_expiration(const tor_cert_t *cert,
+ const char *description,
+ const char *fname) {
+ char expiration[ISO_TIME_LEN+1];
+
+ if (BUG(!cert)) { /* If the specified key hasn't been loaded */
+ log_warn(LD_OR, "No %s key loaded; can't get certificate expiration.",
+ description);
+ } else {
+ format_local_iso_time(expiration, cert->valid_until);
+ log_notice(LD_OR, "The %s certificate stored in %s is valid until %s.",
+ description, fname, expiration);
+ print_cert_expiration(expiration, description);
+ }
+}
+
+/**
+ * Log when our master signing key certificate expires. Used when tor is given
+ * the --key-expiration command-line option.
+ *
+ * Returns 0 on success and 1 on failure.
+ */
+static int
+log_master_signing_key_cert_expiration(const or_options_t *options)
+{
+ const tor_cert_t *signing_key;
+ char *fn = NULL;
+ int failed = 0;
+ time_t now = approx_time();
+
+ fn = options_get_datadir_fname2(options, "keys", "ed25519_signing_cert");
+
+ /* Try to grab our cached copy of the key. */
+ signing_key = get_master_signing_key_cert();
+
+ tor_assert(server_identity_key_is_set());
+
+ /* Load our keys from disk, if necessary. */
+ if (!signing_key) {
+ failed = load_ed_keys(options, now) < 0;
+ signing_key = get_master_signing_key_cert();
+ }
+
+ /* If we do have a signing key, log the expiration time. */
+ if (signing_key) {
+ log_ed_cert_expiration(signing_key, "signing", fn);
+ } else {
+ log_warn(LD_OR, "Could not load signing key certificate from %s, so " \
+ "we couldn't learn anything about certificate expiration.", fn);
+ }
+
+ tor_free(fn);
+
+ return failed;
+}
+
+/**
+ * Log when a key certificate expires. Used when tor is given the
+ * --key-expiration command-line option.
+ *
+ * If an command argument is given, which should specify the type of
+ * key to get expiry information about (currently supported arguments
+ * are "sign"), get info about that type of certificate. Otherwise,
+ * print info about the supported arguments.
+ *
+ * Returns 0 on success and -1 on failure.
+ */
+int
+log_cert_expiration(void)
+{
+ const or_options_t *options = get_options();
+ const char *arg = options->command_arg;
+
+ if (!strcmp(arg, "sign")) {
+ return log_master_signing_key_cert_expiration(options);
+ } else {
+ fprintf(stderr, "No valid argument to --key-expiration found!\n");
+ fprintf(stderr, "Currently recognised arguments are: 'sign'\n");
+
+ return -1;
+ }
+}
+
const ed25519_public_key_t *
get_master_identity_key(void)
{
@@ -1236,7 +1338,9 @@ make_tap_onion_key_crosscert(const crypto_pk_t *onion_key,
uint8_t signed_data[DIGEST_LEN + ED25519_PUBKEY_LEN];
*len_out = 0;
- crypto_pk_get_digest(rsa_id_key, (char*)signed_data);
+ if (crypto_pk_get_digest(rsa_id_key, (char*)signed_data) < 0) {
+ return NULL;
+ }
memcpy(signed_data + DIGEST_LEN, master_id_key->pubkey, ED25519_PUBKEY_LEN);
int r = crypto_pk_private_sign(onion_key,
diff --git a/src/or/routerkeys.h b/src/or/routerkeys.h
index c10cf32a71..0cf13e7600 100644
--- a/src/or/routerkeys.h
+++ b/src/or/routerkeys.h
@@ -63,6 +63,7 @@ MOCK_DECL(int, check_tap_onion_key_crosscert,(const uint8_t *crosscert,
const ed25519_public_key_t *master_id_pkey,
const uint8_t *rsa_id_digest));
+int log_cert_expiration(void);
int load_ed_keys(const or_options_t *options, time_t now);
int should_make_new_ed_keys(const or_options_t *options, const time_t now);
diff --git a/src/or/routerlist.c b/src/or/routerlist.c
index 0e45f63f70..9894019476 100644
--- a/src/or/routerlist.c
+++ b/src/or/routerlist.c
@@ -4971,8 +4971,9 @@ max_dl_per_request(const or_options_t *options, int purpose)
}
/** Don't split our requests so finely that we are requesting fewer than
- * this number per server. */
-#define MIN_DL_PER_REQUEST 4
+ * this number per server. (Grouping more than this at once leads to
+ * diminishing returns.) */
+#define MIN_DL_PER_REQUEST 32
/** To prevent a single screwy cache from confusing us by selective reply,
* try to split our requests into at least this many requests. */
#define MIN_REQUESTS 3
@@ -5033,7 +5034,7 @@ launch_descriptor_downloads(int purpose,
}
}
- if (!authdir_mode_any_nonhidserv(options)) {
+ if (!authdir_mode(options)) {
/* If we wind up going to the authorities, we want to only open one
* connection to each authority at a time, so that we don't overload
* them. We do this by setting PDS_NO_EXISTING_SERVERDESC_FETCH
@@ -5055,8 +5056,9 @@ launch_descriptor_downloads(int purpose,
if (n_per_request > max_dl_per_req)
n_per_request = max_dl_per_req;
- if (n_per_request < MIN_DL_PER_REQUEST)
- n_per_request = MIN_DL_PER_REQUEST;
+ if (n_per_request < MIN_DL_PER_REQUEST) {
+ n_per_request = MIN(MIN_DL_PER_REQUEST, n_downloadable);
+ }
if (n_downloadable > n_per_request)
req_plural = rtr_plural = "s";
@@ -5164,7 +5166,7 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote,
smartlist_add(downloadable, rs->descriptor_digest);
} SMARTLIST_FOREACH_END(rsp);
- if (!authdir_mode_handles_descs(options, ROUTER_PURPOSE_GENERAL)
+ if (!authdir_mode_v3(options)
&& smartlist_len(no_longer_old)) {
routerlist_t *rl = router_get_routerlist();
log_info(LD_DIR, "%d router descriptors listed in consensus are "
diff --git a/src/or/routerparse.c b/src/or/routerparse.c
index 0f6113ccfc..9c11f4d07f 100644
--- a/src/or/routerparse.c
+++ b/src/or/routerparse.c
@@ -1996,7 +1996,6 @@ router_parse_entry_from_string(const char *s, const char *end,
}
tok = find_by_keyword(tokens, K_ROUTER_SIGNATURE);
- note_crypto_pk_op(VERIFY_RTR);
#ifdef COUNT_DISTINCT_DIGESTS
if (!verified_digests)
verified_digests = digestmap_new();
@@ -2231,7 +2230,6 @@ extrainfo_parse_entry_from_string(const char *s, const char *end,
}
if (key) {
- note_crypto_pk_op(VERIFY_RTR);
if (check_signature_token(digest, DIGEST_LEN, tok, key, 0,
"extra-info") < 0)
goto err;
@@ -2708,7 +2706,8 @@ routerstatus_parse_entry_from_string(memarea_t *area,
rs->supports_ed25519_hs_intro =
protocol_list_supports_protocol(tok->args[0], PRT_HSINTRO, 4);
rs->supports_v3_hsdir =
- protocol_list_supports_protocol(tok->args[0], PRT_HSDIR, 2);
+ protocol_list_supports_protocol(tok->args[0], PRT_HSDIR,
+ PROTOVER_HSDIR_V3);
}
if ((tok = find_opt_by_keyword(tokens, K_V))) {
tor_assert(tok->n_args == 1);
@@ -2720,6 +2719,12 @@ routerstatus_parse_entry_from_string(memarea_t *area,
tor_version_as_new_as(tok->args[0], "0.2.4.8-alpha");
rs->protocols_known = 1;
}
+ if (!strcmpstart(tok->args[0], "Tor ") && found_protocol_list) {
+ /* Bug #22447 forces us to filter on this version. */
+ if (!tor_version_as_new_as(tok->args[0], "0.3.0.8")) {
+ rs->supports_v3_hsdir = 0;
+ }
+ }
if (vote_rs) {
vote_rs->version = tor_strdup(tok->args[0]);
}
@@ -2856,7 +2861,6 @@ compare_vote_routerstatus_entries(const void **_a, const void **_b)
int
networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method)
{
- int64_t weight_scale;
int64_t G=0, M=0, E=0, D=0, T=0;
double Wgg, Wgm, Wgd, Wmg, Wmm, Wme, Wmd, Weg, Wem, Wee, Wed;
double Gtotal=0, Mtotal=0, Etotal=0;
@@ -2864,7 +2868,8 @@ networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method)
int valid = 1;
(void) consensus_method;
- weight_scale = networkstatus_get_weight_scale_param(ns);
+ const int64_t weight_scale = networkstatus_get_weight_scale_param(ns);
+ tor_assert(weight_scale >= 1);
Wgg = networkstatus_get_bw_weight(ns, "Wgg", -1);
Wgm = networkstatus_get_bw_weight(ns, "Wgm", -1);
Wgd = networkstatus_get_bw_weight(ns, "Wgd", -1);
@@ -3360,8 +3365,8 @@ extract_shared_random_srvs(networkstatus_t *ns, smartlist_t *tokens)
voter_identity = "consensus";
}
- /* We extract both and on error, everything is stopped because it means
- * the votes is malformed for the shared random value(s). */
+ /* We extract both, and on error everything is stopped because it means
+ * the vote is malformed for the shared random value(s). */
if (extract_one_srv(tokens, K_PREVIOUS_SRV, &ns->sr_info.previous_srv) < 0) {
log_warn(LD_DIR, "SR: Unable to parse previous SRV from %s",
voter_identity);
@@ -5288,12 +5293,14 @@ rend_parse_v2_service_descriptor(rend_service_descriptor_t **parsed_out,
}
/* Parse and verify signature. */
tok = find_by_keyword(tokens, R_SIGNATURE);
- note_crypto_pk_op(VERIFY_RTR);
if (check_signature_token(desc_hash, DIGEST_LEN, tok, result->pk, 0,
"v2 rendezvous service descriptor") < 0)
goto err;
/* Verify that descriptor ID belongs to public key and secret ID part. */
- crypto_pk_get_digest(result->pk, public_key_hash);
+ if (crypto_pk_get_digest(result->pk, public_key_hash) < 0) {
+ log_warn(LD_REND, "Unable to compute rend descriptor public key digest");
+ goto err;
+ }
rend_get_descriptor_id_bytes(test_desc_id, public_key_hash,
secret_id_part);
if (tor_memneq(desc_id_out, test_desc_id, DIGEST_LEN)) {
diff --git a/src/or/scheduler.c b/src/or/scheduler.c
index fac545fba7..0d31c7d58c 100644
--- a/src/or/scheduler.c
+++ b/src/or/scheduler.c
@@ -282,7 +282,7 @@ scheduler_channel_doesnt_want_writes,(channel_t *chan))
*/
smartlist_pqueue_remove(channels_pending,
scheduler_compare_channels,
- STRUCT_OFFSET(channel_t, sched_heap_idx),
+ offsetof(channel_t, sched_heap_idx),
chan);
chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE;
log_debug(LD_SCHED,
@@ -324,7 +324,7 @@ scheduler_channel_has_waiting_cells,(channel_t *chan))
chan->scheduler_state = SCHED_CHAN_PENDING;
smartlist_pqueue_add(channels_pending,
scheduler_compare_channels,
- STRUCT_OFFSET(channel_t, sched_heap_idx),
+ offsetof(channel_t, sched_heap_idx),
chan);
log_debug(LD_SCHED,
"Channel " U64_FORMAT " at %p went from waiting_for_cells "
@@ -400,7 +400,7 @@ scheduler_release_channel,(channel_t *chan))
if (chan->scheduler_state == SCHED_CHAN_PENDING) {
smartlist_pqueue_remove(channels_pending,
scheduler_compare_channels,
- STRUCT_OFFSET(channel_t, sched_heap_idx),
+ offsetof(channel_t, sched_heap_idx),
chan);
}
@@ -430,7 +430,7 @@ scheduler_run, (void))
/* Pop off a channel */
chan = smartlist_pqueue_pop(channels_pending,
scheduler_compare_channels,
- STRUCT_OFFSET(channel_t, sched_heap_idx));
+ offsetof(channel_t, sched_heap_idx));
tor_assert(chan);
/* Figure out how many cells we can write */
@@ -531,7 +531,7 @@ scheduler_run, (void))
readd_chan->scheduler_state = SCHED_CHAN_PENDING;
smartlist_pqueue_add(channels_pending,
scheduler_compare_channels,
- STRUCT_OFFSET(channel_t, sched_heap_idx),
+ offsetof(channel_t, sched_heap_idx),
readd_chan);
} SMARTLIST_FOREACH_END(readd_chan);
smartlist_free(to_readd);
@@ -581,7 +581,7 @@ scheduler_channel_wants_writes(channel_t *chan)
*/
smartlist_pqueue_add(channels_pending,
scheduler_compare_channels,
- STRUCT_OFFSET(channel_t, sched_heap_idx),
+ offsetof(channel_t, sched_heap_idx),
chan);
chan->scheduler_state = SCHED_CHAN_PENDING;
log_debug(LD_SCHED,
@@ -624,11 +624,11 @@ scheduler_touch_channel(channel_t *chan)
/* Remove and re-add it */
smartlist_pqueue_remove(channels_pending,
scheduler_compare_channels,
- STRUCT_OFFSET(channel_t, sched_heap_idx),
+ offsetof(channel_t, sched_heap_idx),
chan);
smartlist_pqueue_add(channels_pending,
scheduler_compare_channels,
- STRUCT_OFFSET(channel_t, sched_heap_idx),
+ offsetof(channel_t, sched_heap_idx),
chan);
}
/* else no-op, since it isn't in the queue */
diff --git a/src/or/shared_random.c b/src/or/shared_random.c
index 25ca0611cd..e4ee64139a 100644
--- a/src/or/shared_random.c
+++ b/src/or/shared_random.c
@@ -1390,6 +1390,52 @@ sr_get_previous_for_control(void)
return srv_str;
}
+/* Return current shared random value from the latest consensus. Caller can
+ * NOT keep a reference to the returned pointer. Return NULL if none. */
+const sr_srv_t *
+sr_get_current(const networkstatus_t *ns)
+{
+ const networkstatus_t *consensus;
+
+ /* Use provided ns else get a live one */
+ if (ns) {
+ consensus = ns;
+ } else {
+ consensus = networkstatus_get_live_consensus(approx_time());
+ }
+ /* Ideally we would never be asked for an SRV without a live consensus. Make
+ * sure this assumption is correct. */
+ tor_assert_nonfatal(consensus);
+
+ if (consensus) {
+ return consensus->sr_info.current_srv;
+ }
+ return NULL;
+}
+
+/* Return previous shared random value from the latest consensus. Caller can
+ * NOT keep a reference to the returned pointer. Return NULL if none. */
+const sr_srv_t *
+sr_get_previous(const networkstatus_t *ns)
+{
+ const networkstatus_t *consensus;
+
+ /* Use provided ns else get a live one */
+ if (ns) {
+ consensus = ns;
+ } else {
+ consensus = networkstatus_get_live_consensus(approx_time());
+ }
+ /* Ideally we would never be asked for an SRV without a live consensus. Make
+ * sure this assumption is correct. */
+ tor_assert_nonfatal(consensus);
+
+ if (consensus) {
+ return consensus->sr_info.previous_srv;
+ }
+ return NULL;
+}
+
#ifdef TOR_UNIT_TESTS
/* Set the global value of number of SRV agreements so the test can play
diff --git a/src/or/shared_random.h b/src/or/shared_random.h
index 1f027c70e0..76d5b95422 100644
--- a/src/or/shared_random.h
+++ b/src/or/shared_random.h
@@ -130,6 +130,9 @@ sr_commit_t *sr_generate_our_commit(time_t timestamp,
char *sr_get_current_for_control(void);
char *sr_get_previous_for_control(void);
+const sr_srv_t *sr_get_current(const networkstatus_t *ns);
+const sr_srv_t *sr_get_previous(const networkstatus_t *ns);
+
#ifdef SHARED_RANDOM_PRIVATE
/* Encode */
diff --git a/src/or/shared_random_state.c b/src/or/shared_random_state.c
index 89d2e8d7f6..5fcf88cc4a 100644
--- a/src/or/shared_random_state.c
+++ b/src/or/shared_random_state.c
@@ -42,7 +42,7 @@ static const char dstate_cur_srv_key[] = "SharedRandCurrentValue";
/* These next two are duplicates or near-duplicates from config.c */
#define VAR(name, conftype, member, initvalue) \
- { name, CONFIG_TYPE_ ## conftype, STRUCT_OFFSET(sr_disk_state_t, member), \
+ { name, CONFIG_TYPE_ ## conftype, offsetof(sr_disk_state_t, member), \
initvalue }
/* As VAR, but the option name and member name are the same. */
#define V(member, conftype, initvalue) \
@@ -77,14 +77,14 @@ static config_var_t state_vars[] = {
* lets us preserve options from versions of Tor newer than us. */
static config_var_t state_extra_var = {
"__extra", CONFIG_TYPE_LINELIST,
- STRUCT_OFFSET(sr_disk_state_t, ExtraLines), NULL
+ offsetof(sr_disk_state_t, ExtraLines), NULL
};
/* Configuration format of sr_disk_state_t. */
static const config_format_t state_format = {
sizeof(sr_disk_state_t),
SR_DISK_STATE_MAGIC,
- STRUCT_OFFSET(sr_disk_state_t, magic_),
+ offsetof(sr_disk_state_t, magic_),
NULL,
NULL,
state_vars,
@@ -133,13 +133,13 @@ get_voting_interval(void)
/* Given the time <b>now</b>, 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. */
-static time_t
+STATIC time_t
get_start_time_of_current_round(time_t now)
{
const or_options_t *options = get_options();
int voting_interval = get_voting_interval();
voting_schedule_t *new_voting_schedule =
- get_voting_schedule(options, now, LOG_INFO);
+ get_voting_schedule(options, now, LOG_DEBUG);
tor_assert(new_voting_schedule);
/* First, get the start time of the next round */
@@ -156,6 +156,42 @@ get_start_time_of_current_round(time_t now)
return curr_start;
}
+/** Return the start time of the current SR protocol run. For example, if the
+ * time is 23/06/2017 23:47:08 and a full SR protocol run is 24 hours, this
+ * function should return 23/06/2017 00:00:00. */
+time_t
+sr_state_get_start_time_of_current_protocol_run(time_t now)
+{
+ int total_rounds = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES;
+ int voting_interval = get_voting_interval();
+ /* Find the time the current round started. */
+ time_t beginning_of_current_round = get_start_time_of_current_round(now);
+
+ /* Get current SR protocol round */
+ int current_round = (now / voting_interval) % total_rounds;
+
+ /* Get start time by subtracting the time elapsed from the beginning of the
+ protocol run */
+ time_t time_elapsed_since_start_of_run = current_round * voting_interval;
+ return beginning_of_current_round - time_elapsed_since_start_of_run;
+}
+
+/** Return the time (in seconds) it takes to complete a full SR protocol phase
+ * (e.g. the commit phase). */
+unsigned int
+sr_state_get_phase_duration(void)
+{
+ return SHARED_RANDOM_N_ROUNDS * get_voting_interval();
+}
+
+/** Return the time (in seconds) it takes to complete a full SR protocol run */
+unsigned int
+sr_state_get_protocol_run_duration(void)
+{
+ int total_protocol_rounds = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES;
+ return total_protocol_rounds * get_voting_interval();
+}
+
/* Return the time we should expire the state file created at <b>now</b>.
* We expire the state file in the beginning of the next protocol run. */
STATIC time_t
diff --git a/src/or/shared_random_state.h b/src/or/shared_random_state.h
index 3526ad47d3..837fa75392 100644
--- a/src/or/shared_random_state.h
+++ b/src/or/shared_random_state.h
@@ -121,11 +121,16 @@ int sr_state_is_initialized(void);
void sr_state_save(void);
void sr_state_free(void);
+time_t sr_state_get_start_time_of_current_protocol_run(time_t now);
+unsigned int sr_state_get_phase_duration(void);
+unsigned int sr_state_get_protocol_run_duration(void);
+
#ifdef SHARED_RANDOM_STATE_PRIVATE
STATIC int disk_state_load_from_disk_impl(const char *fname);
STATIC sr_phase_t get_sr_protocol_phase(time_t valid_after);
+STATIC time_t get_start_time_of_current_round(time_t now);
STATIC time_t get_state_valid_until_time(time_t now);
STATIC const char *get_phase_str(sr_phase_t phase);
diff --git a/src/or/statefile.c b/src/or/statefile.c
index d0606b3012..18111771da 100644
--- a/src/or/statefile.c
+++ b/src/or/statefile.c
@@ -55,7 +55,7 @@ static config_abbrev_t state_abbrevs_[] = {
/*XXXX these next two are duplicates or near-duplicates from config.c */
#define VAR(name,conftype,member,initvalue) \
- { name, CONFIG_TYPE_ ## conftype, STRUCT_OFFSET(or_state_t, member), \
+ { name, CONFIG_TYPE_ ## conftype, offsetof(or_state_t, member), \
initvalue }
/** As VAR, but the option name and member name are the same. */
#define V(member,conftype,initvalue) \
@@ -85,6 +85,8 @@ static config_var_t state_vars_[] = {
VAR("TransportProxy", LINELIST_S, TransportProxies, NULL),
V(TransportProxies, LINELIST_V, NULL),
+ V(HidServRevCounter, LINELIST, NULL),
+
V(BWHistoryReadEnds, ISOTIME, NULL),
V(BWHistoryReadInterval, UINT, "900"),
V(BWHistoryReadValues, CSV, ""),
@@ -131,14 +133,14 @@ static int or_state_validate_cb(void *old_options, void *options,
/** "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 config_var_t state_extra_var = {
- "__extra", CONFIG_TYPE_LINELIST, STRUCT_OFFSET(or_state_t, ExtraLines), NULL
+ "__extra", CONFIG_TYPE_LINELIST, offsetof(or_state_t, ExtraLines), NULL
};
/** Configuration format for or_state_t. */
static const config_format_t state_format = {
sizeof(or_state_t),
OR_STATE_MAGIC,
- STRUCT_OFFSET(or_state_t, magic_),
+ offsetof(or_state_t, magic_),
state_abbrevs_,
NULL,
state_vars_,
diff --git a/src/or/torcert.c b/src/or/torcert.c
index 658e620ca5..69b157446a 100644
--- a/src/or/torcert.c
+++ b/src/or/torcert.c
@@ -393,7 +393,7 @@ rsa_ed25519_crosscert_check(const uint8_t *crosscert,
}
const uint32_t expiration_date = rsa_ed_crosscert_get_expiration(cc);
- const uint64_t expiration_time = expiration_date * 3600;
+ const uint64_t expiration_time = ((uint64_t)expiration_date) * 3600;
if (reject_if_expired_before < 0 ||
expiration_time < (uint64_t)reject_if_expired_before) {