Add support for creating v3 onion services form the control port

This commit is contained in:
Neel Chauhan 2020-11-15 12:56:14 -08:00
parent d425dbf04a
commit eacf528915
5 changed files with 129 additions and 37 deletions

View File

@ -404,6 +404,7 @@ typedef enum rend_auth_type_t {
REND_NO_AUTH = 0, REND_NO_AUTH = 0,
REND_BASIC_AUTH = 1, REND_BASIC_AUTH = 1,
REND_STEALTH_AUTH = 2, REND_STEALTH_AUTH = 2,
REND_V3_AUTH = 3,
} rend_auth_type_t; } rend_auth_type_t;
/** Client-side configuration of authorization for a hidden service. */ /** Client-side configuration of authorization for a hidden service. */

View File

@ -33,6 +33,7 @@
#include "feature/control/control_getinfo.h" #include "feature/control/control_getinfo.h"
#include "feature/control/control_proto.h" #include "feature/control/control_proto.h"
#include "feature/hs/hs_control.h" #include "feature/hs/hs_control.h"
#include "feature/hs/hs_service.h"
#include "feature/nodelist/nodelist.h" #include "feature/nodelist/nodelist.h"
#include "feature/nodelist/routerinfo.h" #include "feature/nodelist/routerinfo.h"
#include "feature/nodelist/routerlist.h" #include "feature/nodelist/routerlist.h"
@ -1653,7 +1654,8 @@ add_onion_helper_add_service(int hs_version,
add_onion_secret_key_t *pk, add_onion_secret_key_t *pk,
smartlist_t *port_cfgs, int max_streams, smartlist_t *port_cfgs, int max_streams,
int max_streams_close_circuit, int auth_type, int max_streams_close_circuit, int auth_type,
smartlist_t *auth_clients, char **address_out) smartlist_t *auth_clients,
smartlist_t *auth_clients_v3, char **address_out)
{ {
hs_service_add_ephemeral_status_t ret; hs_service_add_ephemeral_status_t ret;
@ -1669,7 +1671,8 @@ add_onion_helper_add_service(int hs_version,
break; break;
case HS_VERSION_THREE: case HS_VERSION_THREE:
ret = hs_service_add_ephemeral(pk->v3, port_cfgs, max_streams, ret = hs_service_add_ephemeral(pk->v3, port_cfgs, max_streams,
max_streams_close_circuit, address_out); max_streams_close_circuit,
auth_clients_v3, address_out);
break; break;
default: default:
tor_assert_unreached(); tor_assert_unreached();
@ -1693,7 +1696,7 @@ get_detached_onion_services(void)
} }
static const char *add_onion_keywords[] = { static const char *add_onion_keywords[] = {
"Port", "Flags", "MaxStreams", "ClientAuth", NULL "Port", "Flags", "MaxStreams", "ClientAuth", "ClientAuthV3", NULL
}; };
static const control_cmd_syntax_t add_onion_syntax = { static const control_cmd_syntax_t add_onion_syntax = {
.min_args = 1, .max_args = 1, .min_args = 1, .max_args = 1,
@ -1714,6 +1717,8 @@ handle_control_add_onion(control_connection_t *conn,
smartlist_t *port_cfgs = smartlist_new(); smartlist_t *port_cfgs = smartlist_new();
smartlist_t *auth_clients = NULL; smartlist_t *auth_clients = NULL;
smartlist_t *auth_created_clients = NULL; smartlist_t *auth_created_clients = NULL;
smartlist_t *auth_clients_v3 = NULL;
smartlist_t *auth_clients_v3_str = NULL;
int discard_pk = 0; int discard_pk = 0;
int detach = 0; int detach = 0;
int max_streams = 0; int max_streams = 0;
@ -1758,6 +1763,7 @@ handle_control_add_onion(control_connection_t *conn,
static const char *detach_flag = "Detach"; static const char *detach_flag = "Detach";
static const char *max_s_close_flag = "MaxStreamsCloseCircuit"; static const char *max_s_close_flag = "MaxStreamsCloseCircuit";
static const char *basicauth_flag = "BasicAuth"; static const char *basicauth_flag = "BasicAuth";
static const char *v3auth_flag = "V3Auth";
static const char *non_anonymous_flag = "NonAnonymous"; static const char *non_anonymous_flag = "NonAnonymous";
smartlist_t *flags = smartlist_new(); smartlist_t *flags = smartlist_new();
@ -1778,6 +1784,8 @@ handle_control_add_onion(control_connection_t *conn,
max_streams_close_circuit = 1; max_streams_close_circuit = 1;
} else if (!strcasecmp(flag, basicauth_flag)) { } else if (!strcasecmp(flag, basicauth_flag)) {
auth_type = REND_BASIC_AUTH; auth_type = REND_BASIC_AUTH;
} else if (!strcasecmp(flag, v3auth_flag)) {
auth_type = REND_V3_AUTH;
} else if (!strcasecmp(flag, non_anonymous_flag)) { } else if (!strcasecmp(flag, non_anonymous_flag)) {
non_anonymous = 1; non_anonymous = 1;
} else { } else {
@ -1821,6 +1829,20 @@ handle_control_add_onion(control_connection_t *conn,
if (created) { if (created) {
smartlist_add(auth_created_clients, client); smartlist_add(auth_created_clients, client);
} }
} else if (!strcasecmp(arg->key, "ClientAuthV3")) {
hs_service_authorized_client_t *client_v3 =
parse_authorized_client_key(arg->value);
if (!client_v3) {
goto out;
}
if (auth_clients_v3 == NULL) {
auth_clients_v3 = smartlist_new();
auth_clients_v3_str = smartlist_new();
}
smartlist_add(auth_clients_v3, client_v3);
smartlist_add(auth_clients_v3_str, tor_strdup(arg->value));
} else { } else {
tor_assert_nonfatal_unreached(); tor_assert_nonfatal_unreached();
goto out; goto out;
@ -1829,10 +1851,12 @@ handle_control_add_onion(control_connection_t *conn,
if (smartlist_len(port_cfgs) == 0) { if (smartlist_len(port_cfgs) == 0) {
control_write_endreply(conn, 512, "Missing 'Port' argument"); control_write_endreply(conn, 512, "Missing 'Port' argument");
goto out; goto out;
} else if (auth_type == REND_NO_AUTH && auth_clients != NULL) { } else if (auth_type == REND_NO_AUTH &&
(auth_clients != NULL && auth_clients_v3 != NULL)) {
control_write_endreply(conn, 512, "No auth type specified"); control_write_endreply(conn, 512, "No auth type specified");
goto out; goto out;
} else if (auth_type != REND_NO_AUTH && auth_clients == NULL) { } else if (auth_type != REND_NO_AUTH &&
(auth_clients == NULL && auth_clients_v3 == NULL)) {
control_write_endreply(conn, 512, "No auth clients specified"); control_write_endreply(conn, 512, "No auth clients specified");
goto out; goto out;
} else if ((auth_type == REND_BASIC_AUTH && } else if ((auth_type == REND_BASIC_AUTH &&
@ -1841,6 +1865,15 @@ handle_control_add_onion(control_connection_t *conn,
smartlist_len(auth_clients) > 16)) { smartlist_len(auth_clients) > 16)) {
control_write_endreply(conn, 512, "Too many auth clients"); control_write_endreply(conn, 512, "Too many auth clients");
goto out; goto out;
} else if ((auth_type == REND_BASIC_AUTH ||
auth_type == REND_STEALTH_AUTH) && auth_clients_v3) {
control_write_endreply(conn, 512,
"ClientAuthV3 does not support basic or stealth auth");
goto out;
} else if (auth_type == REND_V3_AUTH && auth_clients) {
control_write_endreply(conn, 512, "ClientAuth does not support v3 auth");
goto out;
} else if (non_anonymous != rend_service_non_anonymous_mode_enabled( } else if (non_anonymous != rend_service_non_anonymous_mode_enabled(
get_options())) { get_options())) {
/* If we failed, and the non-anonymous flag is set, Tor must be in /* If we failed, and the non-anonymous flag is set, Tor must be in
@ -1869,12 +1902,16 @@ handle_control_add_onion(control_connection_t *conn,
goto out; goto out;
} }
/* Hidden service version 3 don't have client authentication support so if /* We can't mix ClientAuth and Version 3 Onion Services, or ClientAuthV3 and
* ClientAuth was given, send back an error. */ * Version 2. If that's the case, send back an error. */
if (hs_version == HS_VERSION_THREE && auth_clients) { if (hs_version == HS_VERSION_THREE && auth_clients) {
control_write_endreply(conn, 513, "ClientAuth not supported"); control_write_endreply(conn, 513, "ClientAuth not supported");
goto out; goto out;
} }
if (hs_version == HS_VERSION_TWO && auth_clients_v3) {
control_write_endreply(conn, 513, "ClientAuthV3 not supported");
goto out;
}
/* Create the HS, using private key pk, client authentication auth_type, /* Create the HS, using private key pk, client authentication auth_type,
* the list of auth_clients, and port config port_cfg. * the list of auth_clients, and port config port_cfg.
@ -1882,12 +1919,13 @@ handle_control_add_onion(control_connection_t *conn,
* regardless of success/failure. * regardless of success/failure.
*/ */
char *service_id = NULL; char *service_id = NULL;
int ret = add_onion_helper_add_service(hs_version, &pk, port_cfgs, int ret =
max_streams, add_onion_helper_add_service(hs_version, &pk, port_cfgs, max_streams,
max_streams_close_circuit, auth_type, max_streams_close_circuit, auth_type,
auth_clients, &service_id); auth_clients, auth_clients_v3, &service_id);
port_cfgs = NULL; /* port_cfgs is now owned by the rendservice code. */ port_cfgs = NULL; /* port_cfgs is now owned by the rendservice code. */
auth_clients = NULL; /* so is auth_clients */ auth_clients = NULL; /* so is auth_clients */
auth_clients_v3 = NULL; /* so is auth_clients_v3 */
switch (ret) { switch (ret) {
case RSAE_OKAY: case RSAE_OKAY:
{ {
@ -1919,6 +1957,11 @@ handle_control_add_onion(control_connection_t *conn,
tor_free(encoded); tor_free(encoded);
}); });
} }
if (auth_clients_v3_str) {
SMARTLIST_FOREACH(auth_clients_v3_str, char *, client_str, {
control_printf_midreply(conn, 250, "ClientAuthV3=%s", client_str);
});
}
send_control_done(conn); send_control_done(conn);
break; break;
@ -1956,6 +1999,18 @@ handle_control_add_onion(control_connection_t *conn,
rend_authorized_client_free(ac)); rend_authorized_client_free(ac));
smartlist_free(auth_clients); smartlist_free(auth_clients);
} }
if (auth_clients_v3) {
SMARTLIST_FOREACH(auth_clients_v3, hs_service_authorized_client_t *, ac,
service_authorized_client_free(ac));
smartlist_free(auth_clients_v3);
}
if (auth_clients_v3_str) {
SMARTLIST_FOREACH(auth_clients_v3_str, char *, client_str,
tor_free(client_str));
smartlist_free(auth_clients_v3_str);
}
if (auth_created_clients) { if (auth_created_clients) {
// Do not free entries; they are the same as auth_clients // Do not free entries; they are the same as auth_clients
smartlist_free(auth_created_clients); smartlist_free(auth_created_clients);

View File

@ -1927,6 +1927,9 @@ rend_auth_type_to_string(rend_auth_type_t auth_type)
case REND_STEALTH_AUTH: case REND_STEALTH_AUTH:
str = "STEALTH_AUTH"; str = "STEALTH_AUTH";
break; break;
case REND_V3_AUTH:
str = "REND_V3_AUTH";
break;
default: default:
str = "UNKNOWN"; str = "UNKNOWN";
} }

View File

@ -1115,6 +1115,43 @@ client_filename_is_valid(const char *filename)
return ret; return ret;
} }
/** Parse an base32-encoded authorized client from a string.
*
* Return the key on success, return NULL, otherwise. */
hs_service_authorized_client_t *
parse_authorized_client_key(const char *key_str)
{
hs_service_authorized_client_t *client = NULL;
/* We expect a specific length of the base32 encoded key so make sure we
* have that so we don't successfully decode a value with a different length
* and end up in trouble when copying the decoded key into a fixed length
* buffer. */
if (strlen(key_str) != BASE32_NOPAD_LEN(CURVE25519_PUBKEY_LEN)) {
log_warn(LD_REND, "Client authorization encoded base32 public key "
"length is invalid: %s", key_str);
goto err;
}
client = tor_malloc_zero(sizeof(hs_service_authorized_client_t));
if (base32_decode((char *) client->client_pk.public_key,
sizeof(client->client_pk.public_key),
key_str, strlen(key_str)) !=
sizeof(client->client_pk.public_key)) {
log_warn(LD_REND, "Client authorization public key cannot be decoded: %s",
key_str);
goto err;
}
return client;
err:
if (client != NULL) {
tor_free(client);
}
return NULL;
}
/** Parse an authorized client from a string. The format of a client string /** Parse an authorized client from a string. The format of a client string
* looks like (see rend-spec-v3.txt): * looks like (see rend-spec-v3.txt):
* *
@ -1161,23 +1198,7 @@ parse_authorized_client(const char *client_key_str)
goto err; goto err;
} }
/* We expect a specific length of the base32 encoded key so make sure we if ((client = parse_authorized_client_key(pubkey_b32)) == NULL) {
* have that so we don't successfully decode a value with a different length
* and end up in trouble when copying the decoded key into a fixed length
* buffer. */
if (strlen(pubkey_b32) != BASE32_NOPAD_LEN(CURVE25519_PUBKEY_LEN)) {
log_warn(LD_REND, "Client authorization encoded base32 public key "
"length is invalid: %s", pubkey_b32);
goto err;
}
client = tor_malloc_zero(sizeof(hs_service_authorized_client_t));
if (base32_decode((char *) client->client_pk.public_key,
sizeof(client->client_pk.public_key),
pubkey_b32, strlen(pubkey_b32)) !=
sizeof(client->client_pk.public_key)) {
log_warn(LD_REND, "Client authorization public key cannot be decoded: %s",
pubkey_b32);
goto err; goto err;
} }
@ -1301,7 +1322,7 @@ load_client_keys(hs_service_t *service)
} }
/** Release all storage held in <b>client</b>. */ /** Release all storage held in <b>client</b>. */
STATIC void void
service_authorized_client_free_(hs_service_authorized_client_t *client) service_authorized_client_free_(hs_service_authorized_client_t *client)
{ {
if (!client) { if (!client) {
@ -3687,7 +3708,8 @@ hs_service_upload_desc_to_dir(const char *encoded_desc,
hs_service_add_ephemeral_status_t hs_service_add_ephemeral_status_t
hs_service_add_ephemeral(ed25519_secret_key_t *sk, smartlist_t *ports, hs_service_add_ephemeral(ed25519_secret_key_t *sk, smartlist_t *ports,
int max_streams_per_rdv_circuit, int max_streams_per_rdv_circuit,
int max_streams_close_circuit, char **address_out) int max_streams_close_circuit,
smartlist_t *auth_clients_v3, char **address_out)
{ {
hs_service_add_ephemeral_status_t ret; hs_service_add_ephemeral_status_t ret;
hs_service_t *service = NULL; hs_service_t *service = NULL;
@ -3731,6 +3753,13 @@ hs_service_add_ephemeral(ed25519_secret_key_t *sk, smartlist_t *ports,
goto err; goto err;
} }
if (service->config.clients == NULL) {
service->config.clients = smartlist_new();
}
SMARTLIST_FOREACH(auth_clients_v3, hs_service_authorized_client_t *, c,
smartlist_add(service->config.clients, c));
/* Build the onion address for logging purposes but also the control port /* Build the onion address for logging purposes but also the control port
* uses it for the HS_DESC event. */ * uses it for the HS_DESC event. */
hs_build_address(&service->keys.identity_pk, hs_build_address(&service->keys.identity_pk,

View File

@ -372,7 +372,8 @@ char *hs_service_lookup_current_desc(const ed25519_public_key_t *pk);
hs_service_add_ephemeral_status_t hs_service_add_ephemeral_status_t
hs_service_add_ephemeral(ed25519_secret_key_t *sk, smartlist_t *ports, hs_service_add_ephemeral(ed25519_secret_key_t *sk, smartlist_t *ports,
int max_streams_per_rdv_circuit, int max_streams_per_rdv_circuit,
int max_streams_close_circuit, char **address_out); int max_streams_close_circuit,
smartlist_t *auth_clients_v3, char **address_out);
int hs_service_del_ephemeral(const char *address); int hs_service_del_ephemeral(const char *address);
/* Used outside of the HS subsystem by the control port command HSPOST. */ /* Used outside of the HS subsystem by the control port command HSPOST. */
@ -388,6 +389,15 @@ hs_service_exports_circuit_id(const ed25519_public_key_t *pk);
void hs_service_dump_stats(int severity); void hs_service_dump_stats(int severity);
void hs_service_circuit_cleanup_on_close(const circuit_t *circ); void hs_service_circuit_cleanup_on_close(const circuit_t *circ);
hs_service_authorized_client_t *
parse_authorized_client_key(const char *key_str);
void
service_authorized_client_free_(hs_service_authorized_client_t *client);
#define service_authorized_client_free(c) \
FREE_AND_NULL(hs_service_authorized_client_t, \
service_authorized_client_free_, (c))
#ifdef HS_SERVICE_PRIVATE #ifdef HS_SERVICE_PRIVATE
#ifdef TOR_UNIT_TESTS #ifdef TOR_UNIT_TESTS
@ -452,12 +462,6 @@ STATIC void service_descriptor_free_(hs_service_descriptor_t *desc);
FREE_AND_NULL(hs_service_descriptor_t, \ FREE_AND_NULL(hs_service_descriptor_t, \
service_descriptor_free_, (d)) service_descriptor_free_, (d))
STATIC void
service_authorized_client_free_(hs_service_authorized_client_t *client);
#define service_authorized_client_free(c) \
FREE_AND_NULL(hs_service_authorized_client_t, \
service_authorized_client_free_, (c))
STATIC int STATIC int
write_address_to_file(const hs_service_t *service, const char *fname_); write_address_to_file(const hs_service_t *service, const char *fname_);