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:
Suphanat Chunhapanya 2018-08-19 08:22:13 +07:00 committed by David Goulet
parent fd6bec923c
commit 8e81fcd51a
6 changed files with 263 additions and 2 deletions

View File

@ -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.");

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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;
}

View File

@ -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) */