mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-11-11 05:33:47 +01:00
hs-v3: Load client authorization secret key from file
The new ClientOnionAuthDir option is introduced which is where tor looks to find the HS v3 client authorization files containing the client private key material. Signed-off-by: David Goulet <dgoulet@torproject.org>
This commit is contained in:
parent
fd6bec923c
commit
8e81fcd51a
@ -450,6 +450,7 @@ static config_var_t option_vars_[] = {
|
||||
VAR("HiddenServiceNumIntroductionPoints", LINELIST_S, RendConfigLines, NULL),
|
||||
VAR("HiddenServiceStatistics", BOOL, HiddenServiceStatistics_option, "1"),
|
||||
V(HidServAuth, LINELIST, NULL),
|
||||
V(ClientOnionAuthDir, FILENAME, NULL),
|
||||
OBSOLETE("CloseHSClientCircuitsImmediatelyOnTimeout"),
|
||||
OBSOLETE("CloseHSServiceRendCircuitsImmediatelyOnTimeout"),
|
||||
V(HiddenServiceSingleHopMode, BOOL, "0"),
|
||||
@ -1917,7 +1918,7 @@ options_act(const or_options_t *old_options)
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
if (running_tor && rend_parse_service_authorization(options, 0) < 0) {
|
||||
if (running_tor && hs_config_client_auth_all(options, 0) < 0) {
|
||||
// LCOV_EXCL_START
|
||||
log_warn(LD_BUG, "Previously validated client authorization for "
|
||||
"hidden services could not be added!");
|
||||
@ -3188,6 +3189,8 @@ warn_about_relative_paths(or_options_t *options)
|
||||
n += warn_if_option_path_is_relative("AccelDir",options->AccelDir);
|
||||
n += warn_if_option_path_is_relative("DataDirectory",options->DataDirectory);
|
||||
n += warn_if_option_path_is_relative("PidFile",options->PidFile);
|
||||
n += warn_if_option_path_is_relative("ClientOnionAuthDir",
|
||||
options->ClientOnionAuthDir);
|
||||
|
||||
for (config_line_t *hs_line = options->RendConfigLines; hs_line;
|
||||
hs_line = hs_line->next) {
|
||||
@ -4339,7 +4342,7 @@ options_validate(or_options_t *old_options, or_options_t *options,
|
||||
REJECT("Failed to configure rendezvous options. See logs for details.");
|
||||
|
||||
/* Parse client-side authorization for hidden services. */
|
||||
if (rend_parse_service_authorization(options, 1) < 0)
|
||||
if (hs_config_client_auth_all(options, 1) < 0)
|
||||
REJECT("Failed to configure client authorization for hidden services. "
|
||||
"See logs for details.");
|
||||
|
||||
|
@ -380,6 +380,8 @@ struct or_options_t {
|
||||
struct config_line_t *HidServAuth; /**< List of configuration lines for
|
||||
* client-side authorizations for hidden
|
||||
* services */
|
||||
char *ClientOnionAuthDir; /**< Directory to keep client
|
||||
* onion service authorization secret keys */
|
||||
char *ContactInfo; /**< Contact info to be published in the directory. */
|
||||
|
||||
int HeartbeatPeriod; /**< Log heartbeat messages after this many seconds
|
||||
|
@ -42,6 +42,10 @@
|
||||
#include "core/or/extend_info_st.h"
|
||||
#include "core/or/origin_circuit_st.h"
|
||||
|
||||
/* Client-side authorizations for hidden services; map of service identity
|
||||
* public key to hs_client_service_authorization_t *. */
|
||||
static digest256map_t *client_auths = NULL;
|
||||
|
||||
/* Return a human-readable string for the client fetch status code. */
|
||||
static const char *
|
||||
fetch_status_to_string(hs_client_fetch_status_t status)
|
||||
@ -1393,6 +1397,216 @@ hs_client_receive_rendezvous_acked(origin_circuit_t *circ,
|
||||
return -1;
|
||||
}
|
||||
|
||||
#define client_service_authorization_free(auth) \
|
||||
FREE_AND_NULL(hs_client_service_authorization_t, \
|
||||
client_service_authorization_free_, (auth))
|
||||
|
||||
static void
|
||||
client_service_authorization_free_(hs_client_service_authorization_t *auth)
|
||||
{
|
||||
if (auth) {
|
||||
memwipe(auth, 0, sizeof(*auth));
|
||||
}
|
||||
tor_free(auth);
|
||||
}
|
||||
|
||||
/** Helper for digest256map_free. */
|
||||
static void
|
||||
client_service_authorization_free_void(void *auth)
|
||||
{
|
||||
client_service_authorization_free_(auth);
|
||||
}
|
||||
|
||||
static void
|
||||
client_service_authorization_free_all(void)
|
||||
{
|
||||
if (!client_auths) {
|
||||
return;
|
||||
}
|
||||
digest256map_free(client_auths, client_service_authorization_free_void);
|
||||
}
|
||||
|
||||
/* Check if the auth key file name is valid or not. Return 1 if valid,
|
||||
* otherwise return 0. */
|
||||
static int
|
||||
auth_key_filename_is_valid(const char *filename)
|
||||
{
|
||||
int ret = 1;
|
||||
const char *valid_extension = ".auth_private";
|
||||
|
||||
tor_assert(filename);
|
||||
|
||||
/* The length of the filename must be greater than the length of the
|
||||
* extension and the valid extension must be at the end of filename. */
|
||||
if (!strcmpend(filename, valid_extension) &&
|
||||
strlen(filename) != strlen(valid_extension)) {
|
||||
ret = 1;
|
||||
} else {
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static hs_client_service_authorization_t *
|
||||
parse_auth_file_content(const char *client_key_str)
|
||||
{
|
||||
char *onion_address = NULL;
|
||||
char *auth_type = NULL;
|
||||
char *key_type = NULL;
|
||||
char *seckey_b32 = NULL;
|
||||
hs_client_service_authorization_t *auth = NULL;
|
||||
smartlist_t *fields = smartlist_new();
|
||||
|
||||
tor_assert(client_key_str);
|
||||
|
||||
smartlist_split_string(fields, client_key_str, ":",
|
||||
SPLIT_SKIP_SPACE, 0);
|
||||
/* Wrong number of fields. */
|
||||
if (smartlist_len(fields) != 4) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
onion_address = smartlist_get(fields, 0);
|
||||
auth_type = smartlist_get(fields, 1);
|
||||
key_type = smartlist_get(fields, 2);
|
||||
seckey_b32 = smartlist_get(fields, 3);
|
||||
|
||||
/* Currently, the only supported auth type is "descriptor" and the only
|
||||
* supported key type is "x25519". */
|
||||
if (strcmp(auth_type, "descriptor") || strcmp(key_type, "x25519")) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
auth = tor_malloc_zero(sizeof(hs_client_service_authorization_t));
|
||||
if (base32_decode((char *) auth->enc_seckey.secret_key,
|
||||
sizeof(auth->enc_seckey.secret_key),
|
||||
seckey_b32, strlen(seckey_b32)) < 0) {
|
||||
goto err;
|
||||
}
|
||||
strncpy(auth->onion_address, onion_address, HS_SERVICE_ADDR_LEN_BASE32);
|
||||
|
||||
/* Success. */
|
||||
goto done;
|
||||
|
||||
err:
|
||||
client_service_authorization_free(auth);
|
||||
done:
|
||||
/* It is also a good idea to wipe the private key. */
|
||||
if (seckey_b32) {
|
||||
memwipe(seckey_b32, 0, strlen(seckey_b32));
|
||||
}
|
||||
if (fields) {
|
||||
SMARTLIST_FOREACH(fields, char *, s, tor_free(s));
|
||||
smartlist_free(fields);
|
||||
}
|
||||
return auth;
|
||||
}
|
||||
|
||||
/* From a set of <b>options</b>, setup every client authorization detail
|
||||
* 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 configuration. */
|
||||
int
|
||||
hs_config_client_authorization(const or_options_t *options,
|
||||
int validate_only)
|
||||
{
|
||||
int ret = -1;
|
||||
digest256map_t *auths = digest256map_new();
|
||||
char *key_dir = NULL;
|
||||
smartlist_t *file_list = NULL;
|
||||
char *client_key_str = NULL;
|
||||
char *client_key_file_path = NULL;
|
||||
|
||||
tor_assert(options);
|
||||
|
||||
/* There is no client auth configured. We can just silently ignore this
|
||||
* function. */
|
||||
if (!options->ClientOnionAuthDir) {
|
||||
ret = 0;
|
||||
goto end;
|
||||
}
|
||||
|
||||
key_dir = tor_strdup(options->ClientOnionAuthDir);
|
||||
|
||||
/* Make sure the directory exists and is private enough. */
|
||||
if (check_private_dir(key_dir, 0, options->User) < 0) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
file_list = tor_listdir(key_dir);
|
||||
if (file_list == NULL) {
|
||||
log_warn(LD_REND, "Client authorization key directory %s can't be listed.",
|
||||
key_dir);
|
||||
goto end;
|
||||
}
|
||||
|
||||
SMARTLIST_FOREACH_BEGIN(file_list, char *, filename) {
|
||||
|
||||
hs_client_service_authorization_t *auth = NULL;
|
||||
ed25519_public_key_t identity_pk;
|
||||
|
||||
if (auth_key_filename_is_valid(filename)) {
|
||||
/* Create a full path for a file. */
|
||||
client_key_file_path = hs_path_from_filename(key_dir, filename);
|
||||
client_key_str = read_file_to_str(client_key_file_path, 0, NULL);
|
||||
/* Free the file path immediately after using it. */
|
||||
tor_free(client_key_file_path);
|
||||
|
||||
/* If we cannot read the file, continue with the next file. */
|
||||
if (!client_key_str) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auth = parse_auth_file_content(client_key_str);
|
||||
/* Free immediately after using it. */
|
||||
tor_free(client_key_str);
|
||||
|
||||
if (auth) {
|
||||
/* Parse the onion address to get an identity public key and use it
|
||||
* as a key of global map in the future. */
|
||||
if (hs_parse_address(auth->onion_address, &identity_pk,
|
||||
NULL, NULL) < 0) {
|
||||
client_service_authorization_free(auth);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (digest256map_get(auths, identity_pk.pubkey)) {
|
||||
client_service_authorization_free(auth);
|
||||
|
||||
log_warn(LD_REND, "Duplicate authorization for the same hidden "
|
||||
"service.");
|
||||
goto end;
|
||||
}
|
||||
|
||||
digest256map_set(auths, identity_pk.pubkey, auth);
|
||||
}
|
||||
}
|
||||
|
||||
} SMARTLIST_FOREACH_END(filename);
|
||||
|
||||
/* Success. */
|
||||
ret = 0;
|
||||
|
||||
end:
|
||||
tor_free(key_dir);
|
||||
tor_free(client_key_str);
|
||||
tor_free(client_key_file_path);
|
||||
if (file_list) {
|
||||
SMARTLIST_FOREACH(file_list, char *, s, tor_free(s));
|
||||
smartlist_free(file_list);
|
||||
}
|
||||
|
||||
if (!validate_only && ret == 0) {
|
||||
client_service_authorization_free_all();
|
||||
client_auths = auths;
|
||||
} else {
|
||||
digest256map_free(auths, client_service_authorization_free_void);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* 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
|
||||
@ -1589,6 +1803,7 @@ hs_client_free_all(void)
|
||||
{
|
||||
/* Purge the hidden service request cache. */
|
||||
hs_purge_last_hid_serv_requests();
|
||||
client_service_authorization_free_all();
|
||||
}
|
||||
|
||||
/* Purge all potentially remotely-detectable state held in the hidden
|
||||
|
@ -31,6 +31,16 @@ typedef enum {
|
||||
HS_CLIENT_FETCH_PENDING = 5,
|
||||
} hs_client_fetch_status_t;
|
||||
|
||||
/** Client-side configuration of authorization for a service. */
|
||||
typedef struct hs_client_service_authorization_t {
|
||||
/* An curve25519 secret key used to compute decryption keys that
|
||||
* allow the client to decrypt the hidden service descriptor. */
|
||||
curve25519_secret_key_t enc_seckey;
|
||||
|
||||
/* An onion address that is used to connect to the onion service. */
|
||||
char onion_address[HS_SERVICE_ADDR_LEN_BASE32+1];
|
||||
} hs_client_service_authorization_t;
|
||||
|
||||
void hs_client_note_connection_attempt_succeeded(
|
||||
const edge_connection_t *conn);
|
||||
|
||||
@ -63,6 +73,9 @@ 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_config_client_authorization(const or_options_t *options,
|
||||
int validate_only);
|
||||
|
||||
int hs_client_reextend_intro_circuit(origin_circuit_t *circ);
|
||||
|
||||
void hs_client_purge_state(void);
|
||||
|
@ -27,7 +27,9 @@
|
||||
|
||||
#include "feature/hs/hs_common.h"
|
||||
#include "feature/hs/hs_config.h"
|
||||
#include "feature/hs/hs_client.h"
|
||||
#include "feature/hs/hs_service.h"
|
||||
#include "feature/rend/rendclient.h"
|
||||
#include "feature/rend/rendservice.h"
|
||||
#include "lib/encoding/confline.h"
|
||||
#include "app/config/or_options_st.h"
|
||||
@ -613,3 +615,28 @@ hs_config_service_all(const or_options_t *options, int validate_only)
|
||||
/* Tor main should call the free all function on error. */
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* From a set of <b>options</b>, setup every client authorization 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 state. */
|
||||
int
|
||||
hs_config_client_auth_all(const or_options_t *options, int validate_only)
|
||||
{
|
||||
int ret = -1;
|
||||
|
||||
/* Configure v2 authorization. */
|
||||
if (rend_parse_service_authorization(options, validate_only) < 0) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Configure v3 authorization. */
|
||||
if (hs_config_client_authorization(options, validate_only) < 0) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Success. */
|
||||
ret = 0;
|
||||
done:
|
||||
return ret;
|
||||
}
|
||||
|
@ -19,6 +19,7 @@
|
||||
/* API */
|
||||
|
||||
int hs_config_service_all(const or_options_t *options, int validate_only);
|
||||
int hs_config_client_auth_all(const or_options_t *options, int validate_only);
|
||||
|
||||
#endif /* !defined(TOR_HS_CONFIG_H) */
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user