From ef2123c7c7bbf0cb6ec2a5528bae082d51ea8962 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Wed, 19 Jun 2019 12:02:41 -0400 Subject: [PATCH] hs-v3: Disallow single hop client to post/get a descriptor Closes #24964 Signed-off-by: David Goulet --- changes/ticket24964 | 4 ++ src/feature/dircache/dircache.c | 13 +++--- src/feature/dircommon/directory.c | 69 +++++++++++++++++++++++++++++++ src/feature/dircommon/directory.h | 1 + src/test/test_hs_cache.c | 23 ++++++++++- 5 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 changes/ticket24964 diff --git a/changes/ticket24964 b/changes/ticket24964 new file mode 100644 index 0000000000..171c86eb1d --- /dev/null +++ b/changes/ticket24964 @@ -0,0 +1,4 @@ + o Minor feature (onion service v3): + - Do not allow single hop client to fetch or post an HS descriptor from an + HSDir. Closes ticket 24964; + diff --git a/src/feature/dircache/dircache.c b/src/feature/dircache/dircache.c index 1b36f716f4..7c6af3582b 100644 --- a/src/feature/dircache/dircache.c +++ b/src/feature/dircache/dircache.c @@ -1390,8 +1390,9 @@ handle_get_hs_descriptor_v3(dir_connection_t *conn, const char *pubkey_str = NULL; const char *url = args->url; - /* Reject unencrypted dir connections */ - if (!connection_dir_is_encrypted(conn)) { + /* Reject non anonymous dir connections (which also tests if encrypted). We + * do not allow single hop clients to query an HSDir. */ + if (!connection_dir_is_anonymous(conn)) { write_short_http_response(conn, 404, "Not found"); goto done; } @@ -1632,10 +1633,10 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers, goto done; } - /* Handle HS descriptor publish request. */ - /* XXX: This should be disabled with a consensus param until we want to - * the prop224 be deployed and thus use. */ - if (connection_dir_is_encrypted(conn) && !strcmpstart(url, "/tor/hs/")) { + /* Handle HS descriptor publish request. We force an anonymous connection + * (which also tests for encrypted). We do not allow single-hop client to + * post a descriptor onto an HSDir. */ + if (connection_dir_is_anonymous(conn) && !strcmpstart(url, "/tor/hs/")) { const char *msg = "HS descriptor stored successfully."; /* We most probably have a publish request for an HS descriptor. */ diff --git a/src/feature/dircommon/directory.c b/src/feature/dircommon/directory.c index 9e6f72e9ac..b3db0aa108 100644 --- a/src/feature/dircommon/directory.c +++ b/src/feature/dircommon/directory.c @@ -7,6 +7,10 @@ #include "app/config/config.h" #include "core/mainloop/connection.h" +#include "core/or/circuitlist.h" +#include "core/or/connection_edge.h" +#include "core/or/connection_or.h" +#include "core/or/channeltls.h" #include "feature/dircache/dircache.h" #include "feature/dircache/dirserv.h" #include "feature/dirclient/dirclient.h" @@ -15,6 +19,10 @@ #include "feature/stats/geoip_stats.h" #include "lib/compress/compress.h" +#include "core/or/circuit_st.h" +#include "core/or/or_circuit_st.h" +#include "core/or/edge_connection_st.h" +#include "core/or/or_connection_st.h" #include "feature/dircommon/dir_connection_st.h" #include "feature/nodelist/routerinfo_st.h" @@ -167,6 +175,67 @@ connection_dir_is_encrypted(const dir_connection_t *conn) return TO_CONN(conn)->linked; } +/** Return true iff the given directory connection dir_conn is + * anonymous, that is, it is on a circuit via a public relay and not directly + * from a client or bridge. + * + * For client circuits via relays: true for 2-hop+ paths. + * For client circuits via bridges: true for 3-hop+ paths. + * + * This first test if the connection is encrypted since it is a strong + * requirement for anonymity. */ +bool +connection_dir_is_anonymous(const dir_connection_t *dir_conn) +{ + const connection_t *conn, *linked_conn; + const edge_connection_t *edge_conn; + const circuit_t *circ; + + tor_assert(dir_conn); + + if (!connection_dir_is_encrypted(dir_conn)) { + return false; + } + + /* + * Buckle up, we'll do a deep dive into the connection in order to get the + * final connection channel of that connection in order to figure out if + * this is a client or relay link. + * + * We go: dir_conn -> linked_conn -> edge_conn -> on_circuit -> p_chan. + */ + + conn = TO_CONN(dir_conn); + linked_conn = conn->linked_conn; + + /* The dir connection should be connected to an edge connection. It can not + * be closed or marked for close. */ + if (linked_conn == NULL || linked_conn->magic != EDGE_CONNECTION_MAGIC || + conn->linked_conn_is_closed || conn->linked_conn->marked_for_close) { + log_info(LD_DIR, "Rejected HSDir request: not linked to edge"); + return false; + } + + edge_conn = TO_EDGE_CONN((connection_t *) linked_conn); + circ = edge_conn->on_circuit; + + /* Can't be a circuit we initiated and without a circuit, no channel. */ + if (circ == NULL || CIRCUIT_IS_ORIGIN(circ)) { + log_info(LD_DIR, "Rejected HSDir request: not on OR circuit"); + return false; + } + + /* Get the previous channel to learn if it is a client or relay link. */ + if (BUG(CONST_TO_OR_CIRCUIT(circ)->p_chan == NULL)) { + log_info(LD_DIR, "Rejected HSDir request: no p_chan"); + return false; + } + + /* Will be true if the channel is an unauthenticated peer which is only true + * for clients and bridges. */ + return !channel_is_client(CONST_TO_OR_CIRCUIT(circ)->p_chan); +} + /** Parse an HTTP request line at the start of a headers string. On failure, * return -1. On success, set *command_out to a copy of the HTTP * command ("get", "post", etc), set *url_out to a copy of the URL, and diff --git a/src/feature/dircommon/directory.h b/src/feature/dircommon/directory.h index ba3f8c1b0e..4fc743ad3d 100644 --- a/src/feature/dircommon/directory.h +++ b/src/feature/dircommon/directory.h @@ -94,6 +94,7 @@ int parse_http_command(const char *headers, char *http_get_header(const char *headers, const char *which); int connection_dir_is_encrypted(const dir_connection_t *conn); +bool connection_dir_is_anonymous(const dir_connection_t *conn); int connection_dir_reached_eof(dir_connection_t *conn); int connection_dir_process_inbuf(dir_connection_t *conn); int connection_dir_finished_flushing(dir_connection_t *conn); diff --git a/src/test/test_hs_cache.c b/src/test/test_hs_cache.c index d71f8b6b18..86ac7e7fb1 100644 --- a/src/test/test_hs_cache.c +++ b/src/test/test_hs_cache.c @@ -10,6 +10,7 @@ #define DIRCACHE_PRIVATE #define DIRCLIENT_PRIVATE #define HS_CACHE_PRIVATE +#define TOR_CHANNEL_INTERNAL_ #include "trunnel/ed25519_cert.h" #include "feature/hs/hs_cache.h" @@ -20,7 +21,12 @@ #include "core/mainloop/connection.h" #include "core/proto/proto_http.h" #include "lib/crypt_ops/crypto_format.h" +#include "core/or/circuitlist.h" +#include "core/or/channel.h" +#include "core/or/edge_connection_st.h" +#include "core/or/or_circuit_st.h" +#include "core/or/or_connection_st.h" #include "feature/dircommon/dir_connection_st.h" #include "feature/nodelist/networkstatus_st.h" @@ -232,6 +238,8 @@ helper_fetch_desc_from_hsdir(const ed25519_public_key_t *blinded_key) /* The dir conn we are going to simulate */ dir_connection_t *conn = NULL; + edge_connection_t *edge_conn = NULL; + or_circuit_t *or_circ = NULL; /* First extract the blinded public key that we are going to use in our query, and then build the actual query string. */ @@ -245,8 +253,16 @@ helper_fetch_desc_from_hsdir(const ed25519_public_key_t *blinded_key) /* Simulate an HTTP GET request to the HSDir */ conn = dir_connection_new(AF_INET); tt_assert(conn); + TO_CONN(conn)->linked = 1; /* Signal that it is encrypted. */ tor_addr_from_ipv4h(&conn->base_.addr, 0x7f000001); - TO_CONN(conn)->linked = 1;/* Pretend the conn is encrypted :) */ + + /* Pretend this conn is anonymous. */ + edge_conn = edge_connection_new(CONN_TYPE_EXIT, AF_INET); + TO_CONN(conn)->linked_conn = TO_CONN(edge_conn); + or_circ = or_circuit_new(0, NULL); + or_circ->p_chan = tor_malloc_zero(sizeof(channel_t)); + edge_conn->on_circuit = TO_CIRCUIT(or_circ); + retval = directory_handle_command_get(conn, hsdir_query_str, NULL, 0); tt_int_op(retval, OP_EQ, 0); @@ -263,8 +279,11 @@ helper_fetch_desc_from_hsdir(const ed25519_public_key_t *blinded_key) done: tor_free(hsdir_query_str); - if (conn) + if (conn) { + tor_free(or_circ->p_chan); + connection_free_minimal(TO_CONN(conn)->linked_conn); connection_free_minimal(TO_CONN(conn)); + } return received_desc; }