mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2025-02-24 22:58:50 +01:00
Merge remote-tracking branch 'dgoulet/ticket20700_035_03'
This commit is contained in:
commit
9ca1af9a87
20 changed files with 2559 additions and 439 deletions
|
@ -459,6 +459,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"),
|
||||
|
@ -1927,7 +1928,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!");
|
||||
|
@ -3193,6 +3194,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) {
|
||||
|
@ -4344,7 +4347,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)
|
||||
|
@ -1177,6 +1181,19 @@ can_client_refetch_desc(const ed25519_public_key_t *identity_pk,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* Return the client auth in the map using the service identity public key.
|
||||
* Return NULL if it does not exist in the map. */
|
||||
static hs_client_service_authorization_t *
|
||||
find_client_auth(const ed25519_public_key_t *service_identity_pk)
|
||||
{
|
||||
/* If the map is not allocated, we can assume that we do not have any client
|
||||
* auth information. */
|
||||
if (!client_auths) {
|
||||
return NULL;
|
||||
}
|
||||
return digest256map_get(client_auths, service_identity_pk->pubkey);
|
||||
}
|
||||
|
||||
/* ========== */
|
||||
/* Public API */
|
||||
/* ========== */
|
||||
|
@ -1215,11 +1232,19 @@ hs_client_decode_descriptor(const char *desc_str,
|
|||
int ret;
|
||||
uint8_t subcredential[DIGEST256_LEN];
|
||||
ed25519_public_key_t blinded_pubkey;
|
||||
hs_client_service_authorization_t *client_auth = NULL;
|
||||
curve25519_secret_key_t *client_auht_sk = NULL;
|
||||
|
||||
tor_assert(desc_str);
|
||||
tor_assert(service_identity_pk);
|
||||
tor_assert(desc);
|
||||
|
||||
/* Check if we have a client authorization for this service in the map. */
|
||||
client_auth = find_client_auth(service_identity_pk);
|
||||
if (client_auth) {
|
||||
client_auht_sk = &client_auth->enc_seckey;
|
||||
}
|
||||
|
||||
/* Create subcredential for this HS so that we can decrypt */
|
||||
{
|
||||
uint64_t current_time_period = hs_get_time_period_num(0);
|
||||
|
@ -1229,7 +1254,8 @@ hs_client_decode_descriptor(const char *desc_str,
|
|||
}
|
||||
|
||||
/* Parse descriptor */
|
||||
ret = hs_desc_decode_descriptor(desc_str, subcredential, desc);
|
||||
ret = hs_desc_decode_descriptor(desc_str, subcredential,
|
||||
client_auht_sk, desc);
|
||||
memwipe(subcredential, 0, sizeof(subcredential));
|
||||
if (ret < 0) {
|
||||
log_warn(LD_GENERAL, "Could not parse received descriptor as client.");
|
||||
|
@ -1393,6 +1419,233 @@ 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;
|
||||
}
|
||||
|
||||
if (strlen(seckey_b32) != BASE32_NOPAD_LEN(CURVE25519_PUBKEY_LEN)) {
|
||||
log_warn(LD_REND, "Client authorization encoded base32 private key "
|
||||
"length is invalid: %s", seckey_b32);
|
||||
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;
|
||||
log_info(LD_REND, "Loading a client authorization key file %s...",
|
||||
filename);
|
||||
|
||||
if (!auth_key_filename_is_valid(filename)) {
|
||||
log_notice(LD_REND, "Client authorization unrecognized filename %s. "
|
||||
"File must end in .auth_private. Ignoring.",
|
||||
filename);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
log_warn(LD_REND, "The file %s cannot be read.", filename);
|
||||
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);
|
||||
log_warn(LD_REND, "The onion address \"%s\" is invalid in "
|
||||
"file %s", filename, auth->onion_address);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (digest256map_get(auths, identity_pk.pubkey)) {
|
||||
client_service_authorization_free(auth);
|
||||
log_warn(LD_REND, "Duplicate authorization for the same hidden "
|
||||
"service address %s.",
|
||||
safe_str_client(auth->onion_address));
|
||||
goto end;
|
||||
}
|
||||
|
||||
digest256map_set(auths, identity_pk.pubkey, auth);
|
||||
log_info(LD_REND, "Loaded a client authorization key file %s.",
|
||||
filename);
|
||||
}
|
||||
} 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 +1842,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
|
||||
|
@ -1621,3 +1875,13 @@ hs_client_dir_info_changed(void)
|
|||
* AP_CONN_STATE_RENDDESC_WAIT state in order to fetch the descriptor. */
|
||||
retry_all_socks_conn_waiting_for_desc();
|
||||
}
|
||||
|
||||
#ifdef TOR_UNIT_TESTS
|
||||
|
||||
STATIC digest256map_t *
|
||||
get_hs_client_auths_map(void)
|
||||
{
|
||||
return client_auths;
|
||||
}
|
||||
|
||||
#endif /* defined(TOR_UNIT_TESTS) */
|
||||
|
|
|
@ -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);
|
||||
|
@ -71,6 +84,11 @@ void hs_client_free_all(void);
|
|||
|
||||
#ifdef HS_CLIENT_PRIVATE
|
||||
|
||||
STATIC int auth_key_filename_is_valid(const char *filename);
|
||||
|
||||
STATIC hs_client_service_authorization_t *
|
||||
parse_auth_file_content(const char *client_key_str);
|
||||
|
||||
STATIC routerstatus_t *
|
||||
pick_hsdir_v3(const ed25519_public_key_t *onion_identity_pk);
|
||||
|
||||
|
@ -86,6 +104,12 @@ STATIC int handle_rendezvous2(origin_circuit_t *circ, const uint8_t *payload,
|
|||
MOCK_DECL(STATIC hs_client_fetch_status_t,
|
||||
fetch_v3_desc, (const ed25519_public_key_t *onion_identity_pk));
|
||||
|
||||
#ifdef TOR_UNIT_TESTS
|
||||
|
||||
STATIC digest256map_t *get_hs_client_auths_map(void);
|
||||
|
||||
#endif /* defined(TOR_UNIT_TESTS) */
|
||||
|
||||
#endif /* defined(HS_CLIENT_PRIVATE) */
|
||||
|
||||
#endif /* !defined(TOR_HS_CLIENT_H) */
|
||||
|
|
|
@ -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) */
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -37,12 +37,6 @@ struct link_specifier_t;
|
|||
#define HS_DESC_CERT_LIFETIME (54 * 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
|
||||
* the encryption key for the encrypted data section of the descriptor. This
|
||||
* adds up to 68 bytes being the blinded key, hashed subcredential and
|
||||
* revision counter. */
|
||||
#define HS_DESC_ENCRYPTED_SECRET_INPUT_LEN \
|
||||
ED25519_PUBKEY_LEN + DIGEST256_LEN + sizeof(uint64_t)
|
||||
/* Length of the KDF output value which is the length of the secret key,
|
||||
* the secret IV and MAC key length which is the length of H() output. */
|
||||
#define HS_DESC_ENCRYPTED_KDF_OUTPUT_LEN \
|
||||
|
@ -59,6 +53,17 @@ struct link_specifier_t;
|
|||
#define HS_DESC_ENCRYPTED_KEY_LEN CIPHER256_KEY_LEN
|
||||
#define HS_DESC_ENCRYPTED_BIT_SIZE (HS_DESC_ENCRYPTED_KEY_LEN * 8)
|
||||
|
||||
/* Length of each components in the auth client section in the descriptor. */
|
||||
#define HS_DESC_CLIENT_ID_LEN 8
|
||||
#define HS_DESC_DESCRIPTOR_COOKIE_LEN 16
|
||||
#define HS_DESC_COOKIE_KEY_LEN 32
|
||||
#define HS_DESC_COOKIE_KEY_BIT_SIZE (HS_DESC_COOKIE_KEY_LEN * 8)
|
||||
#define HS_DESC_ENCRYPED_COOKIE_LEN HS_DESC_DESCRIPTOR_COOKIE_LEN
|
||||
|
||||
/* The number of auth client entries in the descriptor must be the multiple
|
||||
* of this constant. */
|
||||
#define HS_DESC_AUTH_CLIENT_MULTIPLE 16
|
||||
|
||||
/* Type of authentication in the descriptor. */
|
||||
typedef enum {
|
||||
HS_DESC_AUTH_ED25519 = 1
|
||||
|
@ -126,6 +131,20 @@ typedef struct hs_desc_intro_point_t {
|
|||
unsigned int cross_certified : 1;
|
||||
} hs_desc_intro_point_t;
|
||||
|
||||
/* Authorized client information located in a descriptor. */
|
||||
typedef struct hs_desc_authorized_client_t {
|
||||
/* An identifier that the client will use to identify which auth client
|
||||
* entry it needs to use. */
|
||||
uint8_t client_id[HS_DESC_CLIENT_ID_LEN];
|
||||
|
||||
/* An IV that is used to decrypt the encrypted descriptor cookie. */
|
||||
uint8_t iv[CIPHER_IV_LEN];
|
||||
|
||||
/* An encrypted descriptor cookie that the client needs to decrypt to use
|
||||
* it to decrypt the descriptor. */
|
||||
uint8_t encrypted_cookie[HS_DESC_ENCRYPED_COOKIE_LEN];
|
||||
} hs_desc_authorized_client_t;
|
||||
|
||||
/* The encrypted data section of a descriptor. Obviously the data in this is
|
||||
* in plaintext but encrypted once encoded. */
|
||||
typedef struct hs_desc_encrypted_data_t {
|
||||
|
@ -144,6 +163,24 @@ typedef struct hs_desc_encrypted_data_t {
|
|||
smartlist_t *intro_points;
|
||||
} hs_desc_encrypted_data_t;
|
||||
|
||||
/* The superencrypted data section of a descriptor. Obviously the data in
|
||||
* this is in plaintext but encrypted once encoded. */
|
||||
typedef struct hs_desc_superencrypted_data_t {
|
||||
/* This field contains ephemeral x25519 public key which is used by
|
||||
* the encryption scheme in the client authorization. */
|
||||
curve25519_public_key_t auth_ephemeral_pubkey;
|
||||
|
||||
/* A list of authorized clients. Contains hs_desc_authorized_client_t
|
||||
* objects. */
|
||||
smartlist_t *clients;
|
||||
|
||||
/* Decoding only: The b64-decoded encrypted blob from the descriptor */
|
||||
uint8_t *encrypted_blob;
|
||||
|
||||
/* Decoding only: Size of the encrypted_blob */
|
||||
size_t encrypted_blob_size;
|
||||
} hs_desc_superencrypted_data_t;
|
||||
|
||||
/* Plaintext data that is unencrypted information of the descriptor. */
|
||||
typedef struct hs_desc_plaintext_data_t {
|
||||
/* Version of the descriptor format. Spec specifies this field as a
|
||||
|
@ -182,6 +219,11 @@ typedef struct hs_descriptor_t {
|
|||
/* Contains the plaintext part of the descriptor. */
|
||||
hs_desc_plaintext_data_t plaintext_data;
|
||||
|
||||
/* The following contains what's in the superencrypted part of the
|
||||
* descriptor. It's only encrypted in the encoded version of the descriptor
|
||||
* thus the data contained in that object is in plaintext. */
|
||||
hs_desc_superencrypted_data_t superencrypted_data;
|
||||
|
||||
/* The following contains what's in the encrypted part of the descriptor.
|
||||
* It's only encrypted in the encoded version of the descriptor thus the
|
||||
* data contained in that object is in plaintext. */
|
||||
|
@ -211,6 +253,10 @@ void hs_descriptor_free_(hs_descriptor_t *desc);
|
|||
void hs_desc_plaintext_data_free_(hs_desc_plaintext_data_t *desc);
|
||||
#define hs_desc_plaintext_data_free(desc) \
|
||||
FREE_AND_NULL(hs_desc_plaintext_data_t, hs_desc_plaintext_data_free_, (desc))
|
||||
void hs_desc_superencrypted_data_free_(hs_desc_superencrypted_data_t *desc);
|
||||
#define hs_desc_superencrypted_data_free(desc) \
|
||||
FREE_AND_NULL(hs_desc_superencrypted_data_t, \
|
||||
hs_desc_superencrypted_data_free_, (desc))
|
||||
void hs_desc_encrypted_data_free_(hs_desc_encrypted_data_t *desc);
|
||||
#define hs_desc_encrypted_data_free(desc) \
|
||||
FREE_AND_NULL(hs_desc_encrypted_data_t, hs_desc_encrypted_data_free_, (desc))
|
||||
|
@ -226,14 +272,19 @@ 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,
|
||||
const uint8_t *descriptor_cookie,
|
||||
char **encoded_out));
|
||||
|
||||
int hs_desc_decode_descriptor(const char *encoded,
|
||||
const uint8_t *subcredential,
|
||||
const curve25519_secret_key_t *client_auth_sk,
|
||||
hs_descriptor_t **desc_out);
|
||||
int hs_desc_decode_plaintext(const char *encoded,
|
||||
hs_desc_plaintext_data_t *plaintext);
|
||||
int hs_desc_decode_superencrypted(const hs_descriptor_t *desc,
|
||||
hs_desc_superencrypted_data_t *desc_out);
|
||||
int hs_desc_decode_encrypted(const hs_descriptor_t *desc,
|
||||
const curve25519_secret_key_t *client_auth_sk,
|
||||
hs_desc_encrypted_data_t *desc_out);
|
||||
|
||||
size_t hs_desc_obj_size(const hs_descriptor_t *data);
|
||||
|
@ -243,10 +294,27 @@ hs_desc_intro_point_t *hs_desc_intro_point_new(void);
|
|||
void hs_desc_intro_point_free_(hs_desc_intro_point_t *ip);
|
||||
#define hs_desc_intro_point_free(ip) \
|
||||
FREE_AND_NULL(hs_desc_intro_point_t, hs_desc_intro_point_free_, (ip))
|
||||
void hs_desc_authorized_client_free_(hs_desc_authorized_client_t *client);
|
||||
#define hs_desc_authorized_client_free(client) \
|
||||
FREE_AND_NULL(hs_desc_authorized_client_t, \
|
||||
hs_desc_authorized_client_free_, (client))
|
||||
|
||||
link_specifier_t *hs_desc_lspec_to_trunnel(
|
||||
const hs_desc_link_specifier_t *spec);
|
||||
|
||||
hs_desc_authorized_client_t *hs_desc_build_fake_authorized_client(void);
|
||||
void hs_desc_build_authorized_client(const uint8_t *subcredential,
|
||||
const curve25519_public_key_t *
|
||||
client_auth_pk,
|
||||
const curve25519_secret_key_t *
|
||||
auth_ephemeral_sk,
|
||||
const uint8_t *descriptor_cookie,
|
||||
hs_desc_authorized_client_t *client_out);
|
||||
void hs_desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc);
|
||||
void hs_desc_superencrypted_data_free_contents(
|
||||
hs_desc_superencrypted_data_t *desc);
|
||||
void hs_desc_encrypted_data_free_contents(hs_desc_encrypted_data_t *desc);
|
||||
|
||||
#ifdef HS_DESCRIPTOR_PRIVATE
|
||||
|
||||
/* Encoding. */
|
||||
|
@ -265,13 +333,11 @@ STATIC int cert_is_valid(tor_cert_t *cert, uint8_t 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 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);
|
||||
|
||||
MOCK_DECL(STATIC size_t, decrypt_desc_layer,(const hs_descriptor_t *desc,
|
||||
const uint8_t *encrypted_blob,
|
||||
size_t encrypted_blob_size,
|
||||
const uint8_t *descriptor_cookie,
|
||||
int is_superencrypted_layer,
|
||||
char **decrypted_out));
|
||||
|
||||
|
|
|
@ -88,6 +88,7 @@
|
|||
|
||||
/* Onion service directory file names. */
|
||||
static const char fname_keyfile_prefix[] = "hs_ed25519";
|
||||
static const char dname_client_pubkeys[] = "authorized_clients";
|
||||
static const char fname_hostname[] = "hostname";
|
||||
static const char address_tld[] = "onion";
|
||||
|
||||
|
@ -103,9 +104,16 @@ static smartlist_t *hs_service_staging_list;
|
|||
static int consider_republishing_hs_descriptors = 0;
|
||||
|
||||
/* Static declaration. */
|
||||
static int load_client_keys(hs_service_t *service);
|
||||
static void set_descriptor_revision_counter(hs_service_descriptor_t *hs_desc,
|
||||
time_t now, bool is_current);
|
||||
static int build_service_desc_superencrypted(const hs_service_t *service,
|
||||
hs_service_descriptor_t *desc);
|
||||
static void move_descriptors(hs_service_t *src, hs_service_t *dst);
|
||||
static int service_encode_descriptor(const hs_service_t *service,
|
||||
const hs_service_descriptor_t *desc,
|
||||
const ed25519_keypair_t *signing_kp,
|
||||
char **encoded_out);
|
||||
|
||||
/* Helper: Function to compare two objects in the service map. Return 1 if the
|
||||
* two service have the same master public identity key. */
|
||||
|
@ -235,7 +243,7 @@ set_service_default_config(hs_service_config_t *c,
|
|||
|
||||
/* From a service configuration object config, clear everything from it
|
||||
* meaning free allocated pointers and reset the values. */
|
||||
static void
|
||||
STATIC void
|
||||
service_clear_config(hs_service_config_t *config)
|
||||
{
|
||||
if (config == NULL) {
|
||||
|
@ -247,6 +255,11 @@ service_clear_config(hs_service_config_t *config)
|
|||
rend_service_port_config_free(p););
|
||||
smartlist_free(config->ports);
|
||||
}
|
||||
if (config->clients) {
|
||||
SMARTLIST_FOREACH(config->clients, hs_service_authorized_client_t *, p,
|
||||
service_authorized_client_free(p));
|
||||
smartlist_free(config->clients);
|
||||
}
|
||||
memset(config, 0, sizeof(*config));
|
||||
}
|
||||
|
||||
|
@ -1070,6 +1083,11 @@ load_service_keys(hs_service_t *service)
|
|||
goto end;
|
||||
}
|
||||
|
||||
/* Load all client authorization keys in the service. */
|
||||
if (load_client_keys(service) < 0) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Succes. */
|
||||
ret = 0;
|
||||
end:
|
||||
|
@ -1077,6 +1095,223 @@ load_service_keys(hs_service_t *service)
|
|||
return ret;
|
||||
}
|
||||
|
||||
/* Check if the client file name is valid or not. Return 1 if valid,
|
||||
* otherwise return 0. */
|
||||
STATIC int
|
||||
client_filename_is_valid(const char *filename)
|
||||
{
|
||||
int ret = 1;
|
||||
const char *valid_extension = ".auth";
|
||||
|
||||
tor_assert(filename);
|
||||
|
||||
/* The file extension must match and the total filename length can't be the
|
||||
* length of the extension else we do not have a filename. */
|
||||
if (!strcmpend(filename, valid_extension) &&
|
||||
strlen(filename) != strlen(valid_extension)) {
|
||||
ret = 1;
|
||||
} else {
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Parse an authorized client from a string. The format of a client string
|
||||
* looks like (see rend-spec-v3.txt):
|
||||
*
|
||||
* <auth-type>:<key-type>:<base32-encoded-public-key>
|
||||
*
|
||||
* The <auth-type> can only be "descriptor".
|
||||
* The <key-type> can only be "x25519".
|
||||
*
|
||||
* Return the key on success, return NULL, otherwise. */
|
||||
STATIC hs_service_authorized_client_t *
|
||||
parse_authorized_client(const char *client_key_str)
|
||||
{
|
||||
char *auth_type = NULL;
|
||||
char *key_type = NULL;
|
||||
char *pubkey_b32 = NULL;
|
||||
hs_service_authorized_client_t *client = 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) != 3) {
|
||||
log_warn(LD_REND, "Unknown format of client authorization file.");
|
||||
goto err;
|
||||
}
|
||||
|
||||
auth_type = smartlist_get(fields, 0);
|
||||
key_type = smartlist_get(fields, 1);
|
||||
pubkey_b32 = smartlist_get(fields, 2);
|
||||
|
||||
/* Currently, the only supported auth type is "descriptor". */
|
||||
if (strcmp(auth_type, "descriptor")) {
|
||||
log_warn(LD_REND, "Client authorization auth type '%s' not supported.",
|
||||
auth_type);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Currently, the only supported key type is "x25519". */
|
||||
if (strcmp(key_type, "x25519")) {
|
||||
log_warn(LD_REND, "Client authorization key type '%s' not supported.",
|
||||
key_type);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* 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(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)) < 0) {
|
||||
log_warn(LD_REND, "Client authorization public key cannot be decoded: %s",
|
||||
pubkey_b32);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Success. */
|
||||
goto done;
|
||||
|
||||
err:
|
||||
service_authorized_client_free(client);
|
||||
done:
|
||||
/* It is also a good idea to wipe the public key. */
|
||||
if (pubkey_b32) {
|
||||
memwipe(pubkey_b32, 0, strlen(pubkey_b32));
|
||||
}
|
||||
if (fields) {
|
||||
SMARTLIST_FOREACH(fields, char *, s, tor_free(s));
|
||||
smartlist_free(fields);
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
/* Load all the client public keys for the given service. Return 0 on
|
||||
* success else -1 on failure. */
|
||||
static int
|
||||
load_client_keys(hs_service_t *service)
|
||||
{
|
||||
int ret = -1;
|
||||
char *client_key_str = NULL;
|
||||
char *client_key_file_path = NULL;
|
||||
char *client_keys_dir_path = NULL;
|
||||
hs_service_config_t *config;
|
||||
smartlist_t *file_list = NULL;
|
||||
|
||||
tor_assert(service);
|
||||
|
||||
config = &service->config;
|
||||
|
||||
/* Before calling this function, we already call load_service_keys to make
|
||||
* sure that the directory exists with the right permission. So, if we
|
||||
* cannot create a client pubkey key directory, we consider it as a bug. */
|
||||
client_keys_dir_path = hs_path_from_filename(config->directory_path,
|
||||
dname_client_pubkeys);
|
||||
if (BUG(hs_check_service_private_dir(get_options()->User,
|
||||
client_keys_dir_path,
|
||||
config->dir_group_readable, 1) < 0)) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* If the list of clients already exists, we must clear it first. */
|
||||
if (config->clients) {
|
||||
SMARTLIST_FOREACH(config->clients, hs_service_authorized_client_t *, p,
|
||||
service_authorized_client_free(p));
|
||||
smartlist_free(config->clients);
|
||||
}
|
||||
|
||||
config->clients = smartlist_new();
|
||||
|
||||
file_list = tor_listdir(client_keys_dir_path);
|
||||
if (file_list == NULL) {
|
||||
log_warn(LD_REND, "Client authorization directory %s can't be listed.",
|
||||
client_keys_dir_path);
|
||||
goto end;
|
||||
}
|
||||
|
||||
SMARTLIST_FOREACH_BEGIN(file_list, const char *, filename) {
|
||||
hs_service_authorized_client_t *client = NULL;
|
||||
log_info(LD_REND, "Loading a client authorization key file %s...",
|
||||
filename);
|
||||
|
||||
if (!client_filename_is_valid(filename)) {
|
||||
log_warn(LD_REND, "Client authorization unrecognized filename %s. "
|
||||
"File must end in .auth. Ignoring.", filename);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Create a full path for a file. */
|
||||
client_key_file_path = hs_path_from_filename(client_keys_dir_path,
|
||||
filename);
|
||||
client_key_str = read_file_to_str(client_key_file_path, 0, NULL);
|
||||
/* Free 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) {
|
||||
log_warn(LD_REND, "Client authorization file %s can't be read. "
|
||||
"Corrupted or verify permission? Ignoring.",
|
||||
client_key_file_path);
|
||||
continue;
|
||||
}
|
||||
|
||||
client = parse_authorized_client(client_key_str);
|
||||
/* Wipe and free immediately after using it. */
|
||||
memwipe(client_key_str, 0, strlen(client_key_str));
|
||||
tor_free(client_key_str);
|
||||
|
||||
if (client) {
|
||||
smartlist_add(config->clients, client);
|
||||
log_info(LD_REND, "Loaded a client authorization key file %s.",
|
||||
filename);
|
||||
}
|
||||
|
||||
} SMARTLIST_FOREACH_END(filename);
|
||||
|
||||
/* If the number of clients is greater than zero, set the flag to be true. */
|
||||
if (smartlist_len(config->clients) > 0) {
|
||||
config->is_client_auth_enabled = 1;
|
||||
}
|
||||
|
||||
/* Success. */
|
||||
ret = 0;
|
||||
end:
|
||||
if (client_key_str) {
|
||||
memwipe(client_key_str, 0, strlen(client_key_str));
|
||||
}
|
||||
if (file_list) {
|
||||
SMARTLIST_FOREACH(file_list, char *, s, tor_free(s));
|
||||
smartlist_free(file_list);
|
||||
}
|
||||
tor_free(client_key_str);
|
||||
tor_free(client_key_file_path);
|
||||
tor_free(client_keys_dir_path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
STATIC void
|
||||
service_authorized_client_free_(hs_service_authorized_client_t *client)
|
||||
{
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
memwipe(&client->client_pk, 0, sizeof(client->client_pk));
|
||||
tor_free(client);
|
||||
}
|
||||
|
||||
/* Free a given service descriptor object and all key material is wiped. */
|
||||
STATIC void
|
||||
service_descriptor_free_(hs_service_descriptor_t *desc)
|
||||
|
@ -1111,8 +1346,113 @@ service_descriptor_new(void)
|
|||
return sdesc;
|
||||
}
|
||||
|
||||
/* Move descriptor(s) from the src service to the dst service. We do this
|
||||
* during SIGHUP when we re-create our hidden services. */
|
||||
/* Allocate and return a deep copy of client. */
|
||||
static hs_service_authorized_client_t *
|
||||
service_authorized_client_dup(const hs_service_authorized_client_t *client)
|
||||
{
|
||||
hs_service_authorized_client_t *client_dup = NULL;
|
||||
|
||||
tor_assert(client);
|
||||
|
||||
client_dup = tor_malloc_zero(sizeof(hs_service_authorized_client_t));
|
||||
/* Currently, the public key is the only component of
|
||||
* hs_service_authorized_client_t. */
|
||||
memcpy(client_dup->client_pk.public_key,
|
||||
client->client_pk.public_key,
|
||||
CURVE25519_PUBKEY_LEN);
|
||||
|
||||
return client_dup;
|
||||
}
|
||||
|
||||
/* If two authorized clients are equal, return 0. If the first one should come
|
||||
* before the second, return less than zero. If the first should come after
|
||||
* the second, return greater than zero. */
|
||||
static int
|
||||
service_authorized_client_cmp(const hs_service_authorized_client_t *client1,
|
||||
const hs_service_authorized_client_t *client2)
|
||||
{
|
||||
tor_assert(client1);
|
||||
tor_assert(client2);
|
||||
|
||||
/* Currently, the public key is the only component of
|
||||
* hs_service_authorized_client_t. */
|
||||
return tor_memcmp(client1->client_pk.public_key,
|
||||
client2->client_pk.public_key,
|
||||
CURVE25519_PUBKEY_LEN);
|
||||
}
|
||||
|
||||
/* Helper for sorting authorized clients. */
|
||||
static int
|
||||
compare_service_authorzized_client_(const void **_a, const void **_b)
|
||||
{
|
||||
const hs_service_authorized_client_t *a = *_a, *b = *_b;
|
||||
return service_authorized_client_cmp(a, b);
|
||||
}
|
||||
|
||||
/* If the list of hs_service_authorized_client_t's is different between
|
||||
* src and dst, return 1. Otherwise, return 0. */
|
||||
STATIC int
|
||||
service_authorized_client_config_equal(const hs_service_config_t *config1,
|
||||
const hs_service_config_t *config2)
|
||||
{
|
||||
int ret = 0;
|
||||
int i;
|
||||
smartlist_t *sl1 = smartlist_new();
|
||||
smartlist_t *sl2 = smartlist_new();
|
||||
|
||||
tor_assert(config1);
|
||||
tor_assert(config2);
|
||||
tor_assert(config1->clients);
|
||||
tor_assert(config2->clients);
|
||||
|
||||
/* If the number of clients is different, it is obvious that the list
|
||||
* changes. */
|
||||
if (smartlist_len(config1->clients) != smartlist_len(config2->clients)) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* We do not want to mutate config1 and config2, so we will duplicate both
|
||||
* entire client lists here. */
|
||||
SMARTLIST_FOREACH(config1->clients,
|
||||
hs_service_authorized_client_t *, client,
|
||||
smartlist_add(sl1, service_authorized_client_dup(client)));
|
||||
|
||||
SMARTLIST_FOREACH(config2->clients,
|
||||
hs_service_authorized_client_t *, client,
|
||||
smartlist_add(sl2, service_authorized_client_dup(client)));
|
||||
|
||||
smartlist_sort(sl1, compare_service_authorzized_client_);
|
||||
smartlist_sort(sl2, compare_service_authorzized_client_);
|
||||
|
||||
for (i = 0; i < smartlist_len(sl1); i++) {
|
||||
/* If the clients at index i in both lists differ, the whole configs
|
||||
* differ. */
|
||||
if (service_authorized_client_cmp(smartlist_get(sl1, i),
|
||||
smartlist_get(sl2, i))) {
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
/* Success. */
|
||||
ret = 1;
|
||||
|
||||
done:
|
||||
if (sl1) {
|
||||
SMARTLIST_FOREACH(sl1, hs_service_authorized_client_t *, p,
|
||||
service_authorized_client_free(p));
|
||||
smartlist_free(sl1);
|
||||
}
|
||||
if (sl2) {
|
||||
SMARTLIST_FOREACH(sl2, hs_service_authorized_client_t *, p,
|
||||
service_authorized_client_free(p));
|
||||
smartlist_free(sl2);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Move descriptor(s) from the src service to the dst service and modify their
|
||||
* content if necessary. We do this during SIGHUP when we re-create our
|
||||
* hidden services. */
|
||||
static void
|
||||
move_descriptors(hs_service_t *src, hs_service_t *dst)
|
||||
{
|
||||
|
@ -1136,6 +1476,37 @@ move_descriptors(hs_service_t *src, hs_service_t *dst)
|
|||
dst->desc_next = src->desc_next;
|
||||
src->desc_next = NULL;
|
||||
}
|
||||
|
||||
/* If the client authorization changes, we must rebuild the superencrypted
|
||||
* section and republish the descriptors. */
|
||||
int client_auth_changed =
|
||||
!service_authorized_client_config_equal(&src->config, &dst->config);
|
||||
if (client_auth_changed && dst->desc_current) {
|
||||
/* We have to clear the superencrypted content first. */
|
||||
hs_desc_superencrypted_data_free_contents(
|
||||
&dst->desc_current->desc->superencrypted_data);
|
||||
if (build_service_desc_superencrypted(dst, dst->desc_current) < 0) {
|
||||
goto err;
|
||||
}
|
||||
service_desc_schedule_upload(dst->desc_current, time(NULL), 1);
|
||||
}
|
||||
if (client_auth_changed && dst->desc_next) {
|
||||
/* We have to clear the superencrypted content first. */
|
||||
hs_desc_superencrypted_data_free_contents(
|
||||
&dst->desc_next->desc->superencrypted_data);
|
||||
if (build_service_desc_superencrypted(dst, dst->desc_next) < 0) {
|
||||
goto err;
|
||||
}
|
||||
service_desc_schedule_upload(dst->desc_next, time(NULL), 1);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
err:
|
||||
/* If there is an error, free all descriptors to make it clean and generate
|
||||
* them later. */
|
||||
service_descriptor_free(dst->desc_current);
|
||||
service_descriptor_free(dst->desc_next);
|
||||
}
|
||||
|
||||
/* From the given service, remove all expired failing intro points for each
|
||||
|
@ -1353,6 +1724,85 @@ build_service_desc_encrypted(const hs_service_t *service,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* Populate the descriptor superencrypted section from the given service
|
||||
* object. This will generate a valid list of hs_desc_authorized_client_t
|
||||
* of clients that are authorized to use the service. Return 0 on success
|
||||
* else -1 on error. */
|
||||
static int
|
||||
build_service_desc_superencrypted(const hs_service_t *service,
|
||||
hs_service_descriptor_t *desc)
|
||||
{
|
||||
const hs_service_config_t *config;
|
||||
int i;
|
||||
hs_desc_superencrypted_data_t *superencrypted;
|
||||
|
||||
tor_assert(service);
|
||||
tor_assert(desc);
|
||||
|
||||
superencrypted = &desc->desc->superencrypted_data;
|
||||
config = &service->config;
|
||||
|
||||
/* The ephemeral key pair is already generated, so this should not give
|
||||
* an error. */
|
||||
if (BUG(!curve25519_public_key_is_ok(&desc->auth_ephemeral_kp.pubkey))) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(&superencrypted->auth_ephemeral_pubkey,
|
||||
&desc->auth_ephemeral_kp.pubkey,
|
||||
sizeof(curve25519_public_key_t));
|
||||
|
||||
/* Test that subcred is not zero because we might use it below */
|
||||
if (BUG(tor_mem_is_zero((char*)desc->desc->subcredential, DIGEST256_LEN))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Create a smartlist to store clients */
|
||||
superencrypted->clients = smartlist_new();
|
||||
|
||||
/* We do not need to build the desc authorized client if the client
|
||||
* authorization is disabled */
|
||||
if (config->is_client_auth_enabled) {
|
||||
SMARTLIST_FOREACH_BEGIN(config->clients,
|
||||
hs_service_authorized_client_t *, client) {
|
||||
hs_desc_authorized_client_t *desc_client;
|
||||
desc_client = tor_malloc_zero(sizeof(hs_desc_authorized_client_t));
|
||||
|
||||
/* Prepare the client for descriptor and then add to the list in the
|
||||
* superencrypted part of the descriptor */
|
||||
hs_desc_build_authorized_client(desc->desc->subcredential,
|
||||
&client->client_pk,
|
||||
&desc->auth_ephemeral_kp.seckey,
|
||||
desc->descriptor_cookie, desc_client);
|
||||
smartlist_add(superencrypted->clients, desc_client);
|
||||
|
||||
} SMARTLIST_FOREACH_END(client);
|
||||
}
|
||||
|
||||
/* We cannot let the number of auth-clients to be zero, so we need to
|
||||
* make it be 16. If it is already a multiple of 16, we do not need to
|
||||
* do anything. Otherwise, add the additional ones to make it a
|
||||
* multiple of 16. */
|
||||
int num_clients = smartlist_len(superencrypted->clients);
|
||||
int num_clients_to_add;
|
||||
if (num_clients == 0) {
|
||||
num_clients_to_add = HS_DESC_AUTH_CLIENT_MULTIPLE;
|
||||
} else if (num_clients % HS_DESC_AUTH_CLIENT_MULTIPLE == 0) {
|
||||
num_clients_to_add = 0;
|
||||
} else {
|
||||
num_clients_to_add =
|
||||
HS_DESC_AUTH_CLIENT_MULTIPLE
|
||||
- (num_clients % HS_DESC_AUTH_CLIENT_MULTIPLE);
|
||||
}
|
||||
|
||||
for (i = 0; i < num_clients_to_add; i++) {
|
||||
hs_desc_authorized_client_t *desc_client =
|
||||
hs_desc_build_fake_authorized_client();
|
||||
smartlist_add(superencrypted->clients, desc_client);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Populate 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. */
|
||||
|
@ -1418,13 +1868,14 @@ generate_ope_cipher_for_desc(const hs_service_descriptor_t *hs_desc)
|
|||
}
|
||||
|
||||
/* 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. */
|
||||
* is the blinded keypair, the descriptor signing keypair, the ephemeral
|
||||
* keypair, and the descriptor cookie. 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)
|
||||
{
|
||||
int ret = 0;
|
||||
int ret = -1;
|
||||
ed25519_keypair_t kp;
|
||||
|
||||
tor_assert(desc);
|
||||
|
@ -1455,9 +1906,28 @@ build_service_desc_keys(const hs_service_t *service,
|
|||
log_warn(LD_REND, "Can't generate descriptor signing keypair for "
|
||||
"service %s",
|
||||
safe_str_client(service->onion_address));
|
||||
ret = -1;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* No need for extra strong, this is a temporary key only for this
|
||||
* descriptor. Nothing long term. */
|
||||
if (curve25519_keypair_generate(&desc->auth_ephemeral_kp, 0) < 0) {
|
||||
log_warn(LD_REND, "Can't generate auth ephemeral keypair for "
|
||||
"service %s",
|
||||
safe_str_client(service->onion_address));
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Random a descriptor cookie to be used as a part of a key to encrypt the
|
||||
* descriptor, if the client auth is enabled. */
|
||||
if (service->config.is_client_auth_enabled) {
|
||||
crypto_strongest_rand(desc->descriptor_cookie,
|
||||
sizeof(desc->descriptor_cookie));
|
||||
}
|
||||
|
||||
/* Success. */
|
||||
ret = 0;
|
||||
end:
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -1491,6 +1961,10 @@ build_service_descriptor(hs_service_t *service, time_t now,
|
|||
if (build_service_desc_plaintext(service, desc, now) < 0) {
|
||||
goto err;
|
||||
}
|
||||
/* Setup superencrypted descriptor content. */
|
||||
if (build_service_desc_superencrypted(service, desc) < 0) {
|
||||
goto err;
|
||||
}
|
||||
/* Setup encrypted descriptor content. */
|
||||
if (build_service_desc_encrypted(service, desc) < 0) {
|
||||
goto err;
|
||||
|
@ -1499,7 +1973,7 @@ build_service_descriptor(hs_service_t *service, time_t now,
|
|||
/* 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,
|
||||
if (BUG(service_encode_descriptor(service, desc, &desc->signing_kp,
|
||||
&encoded_desc) < 0)) {
|
||||
goto err;
|
||||
}
|
||||
|
@ -2338,7 +2812,7 @@ upload_descriptor_to_hsdir(const hs_service_t *service,
|
|||
|
||||
/* 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,
|
||||
if (BUG(service_encode_descriptor(service, desc, &desc->signing_kp,
|
||||
&encoded_desc) < 0)) {
|
||||
goto end;
|
||||
}
|
||||
|
@ -2904,6 +3378,34 @@ service_key_on_disk(const char *directory_path)
|
|||
|
||||
ed25519_keypair_free(kp);
|
||||
tor_free(fname);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* This is a proxy function before actually calling hs_desc_encode_descriptor
|
||||
* because we need some preprocessing here */
|
||||
static int
|
||||
service_encode_descriptor(const hs_service_t *service,
|
||||
const hs_service_descriptor_t *desc,
|
||||
const ed25519_keypair_t *signing_kp,
|
||||
char **encoded_out)
|
||||
{
|
||||
int ret;
|
||||
const uint8_t *descriptor_cookie = NULL;
|
||||
|
||||
tor_assert(service);
|
||||
tor_assert(desc);
|
||||
tor_assert(encoded_out);
|
||||
|
||||
/* If the client authorization is enabled, send the descriptor cookie to
|
||||
* hs_desc_encode_descriptor. Otherwise, send NULL */
|
||||
if (service->config.is_client_auth_enabled) {
|
||||
descriptor_cookie = desc->descriptor_cookie;
|
||||
}
|
||||
|
||||
ret = hs_desc_encode_descriptor(desc->desc, signing_kp,
|
||||
descriptor_cookie, encoded_out);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -3114,7 +3616,8 @@ hs_service_lookup_current_desc(const ed25519_public_key_t *pk)
|
|||
/* No matter what is the result (which should never be a failure), return
|
||||
* the encoded variable, if success it will contain the right thing else
|
||||
* it will be NULL. */
|
||||
hs_desc_encode_descriptor(service->desc_current->desc,
|
||||
service_encode_descriptor(service,
|
||||
service->desc_current,
|
||||
&service->desc_current->signing_kp,
|
||||
&encoded_desc);
|
||||
return encoded_desc;
|
||||
|
@ -3281,6 +3784,7 @@ hs_service_lists_fnames_for_sandbox(smartlist_t *file_list,
|
|||
}
|
||||
service_add_fnames_to_list(service, file_list);
|
||||
smartlist_add_strdup(dir_list, service->config.directory_path);
|
||||
smartlist_add_strdup(dir_list, dname_client_pubkeys);
|
||||
} FOR_EACH_DESCRIPTOR_END;
|
||||
}
|
||||
|
||||
|
@ -3451,7 +3955,6 @@ hs_service_load_all_keys(void)
|
|||
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
|
||||
|
|
|
@ -105,6 +105,13 @@ typedef struct hs_service_descriptor_t {
|
|||
* publishes the descriptor. */
|
||||
hs_descriptor_t *desc;
|
||||
|
||||
/* Client authorization ephemeral keypair. */
|
||||
curve25519_keypair_t auth_ephemeral_kp;
|
||||
|
||||
/* Descriptor cookie used to encrypt the descriptor, when the client
|
||||
* authorization is enabled */
|
||||
uint8_t descriptor_cookie[HS_DESC_DESCRIPTOR_COOKIE_LEN];
|
||||
|
||||
/* Descriptor signing keypair. */
|
||||
ed25519_keypair_t signing_kp;
|
||||
|
||||
|
@ -148,6 +155,12 @@ typedef struct hs_service_keys_t {
|
|||
unsigned int is_identify_key_offline : 1;
|
||||
} hs_service_keys_t;
|
||||
|
||||
/** Service side configuration of client authorization. */
|
||||
typedef struct hs_service_authorized_client_t {
|
||||
/* The client auth public key used to encrypt the descriptor cookie. */
|
||||
curve25519_public_key_t client_pk;
|
||||
} hs_service_authorized_client_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. */
|
||||
|
@ -176,6 +189,13 @@ typedef struct hs_service_config_t {
|
|||
* HiddenServiceNumIntroductionPoints option. */
|
||||
unsigned int num_intro_points;
|
||||
|
||||
/* True iff the client auth is enabled. */
|
||||
unsigned int is_client_auth_enabled : 1;
|
||||
|
||||
/* List of hs_service_authorized_client_t's of clients that may access this
|
||||
* service. Specified by HiddenServiceAuthorizeClient option. */
|
||||
smartlist_t *clients;
|
||||
|
||||
/* True iff we allow request made on unknown ports. Specified by
|
||||
* HiddenServiceAllowUnknownPorts option. */
|
||||
unsigned int allow_unknown_ports : 1;
|
||||
|
@ -336,6 +356,9 @@ 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 int client_filename_is_valid(const char *filename);
|
||||
STATIC hs_service_authorized_client_t *
|
||||
parse_authorized_client(const char *client_key_str);
|
||||
STATIC void get_objects_from_ident(const hs_ident_circuit_t *ident,
|
||||
hs_service_t **service,
|
||||
hs_service_intro_point_t **ip,
|
||||
|
@ -356,6 +379,13 @@ STATIC void service_descriptor_free_(hs_service_descriptor_t *desc);
|
|||
#define service_descriptor_free(d) \
|
||||
FREE_AND_NULL(hs_service_descriptor_t, \
|
||||
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
|
||||
write_address_to_file(const hs_service_t *service, const char *fname_);
|
||||
|
||||
|
@ -369,6 +399,12 @@ STATIC void service_desc_schedule_upload(hs_service_descriptor_t *desc,
|
|||
STATIC int service_desc_hsdirs_changed(const hs_service_t *service,
|
||||
const hs_service_descriptor_t *desc);
|
||||
|
||||
STATIC int service_authorized_client_config_equal(
|
||||
const hs_service_config_t *config1,
|
||||
const hs_service_config_t *config2);
|
||||
|
||||
STATIC void service_clear_config(hs_service_config_t *config);
|
||||
|
||||
#endif /* defined(HS_SERVICE_PRIVATE) */
|
||||
|
||||
#endif /* !defined(TOR_HS_SERVICE_H) */
|
||||
|
|
|
@ -335,8 +335,8 @@ crypto_strongest_rand_raw(uint8_t *out, size_t out_len)
|
|||
* Try to get <b>out_len</b> bytes of the strongest entropy we can generate,
|
||||
* storing it into <b>out</b>.
|
||||
**/
|
||||
void
|
||||
crypto_strongest_rand(uint8_t *out, size_t out_len)
|
||||
MOCK_IMPL(void,
|
||||
crypto_strongest_rand,(uint8_t *out, size_t out_len))
|
||||
{
|
||||
#define DLEN DIGEST512_LEN
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
int crypto_seed_rng(void) ATTR_WUR;
|
||||
MOCK_DECL(void,crypto_rand,(char *to, size_t n));
|
||||
void crypto_rand_unmocked(char *to, size_t n);
|
||||
void crypto_strongest_rand(uint8_t *out, size_t out_len);
|
||||
MOCK_DECL(void,crypto_strongest_rand,(uint8_t *out, size_t out_len));
|
||||
int crypto_rand_int(unsigned int max);
|
||||
int crypto_rand_int_range(unsigned int min, unsigned int max);
|
||||
uint64_t crypto_rand_uint64_range(uint64_t min, uint64_t max);
|
||||
|
|
|
@ -38,11 +38,13 @@ static size_t
|
|||
mock_decrypt_desc_layer(const hs_descriptor_t *desc,
|
||||
const uint8_t *encrypted_blob,
|
||||
size_t encrypted_blob_size,
|
||||
const uint8_t *descriptor_cookie,
|
||||
int is_superencrypted_layer,
|
||||
char **decrypted_out)
|
||||
{
|
||||
(void)is_superencrypted_layer;
|
||||
(void)desc;
|
||||
(void)descriptor_cookie;
|
||||
const size_t overhead = HS_DESC_ENCRYPTED_SALT_LEN + DIGEST256_LEN;
|
||||
if (encrypted_blob_size < overhead)
|
||||
return 0;
|
||||
|
@ -84,7 +86,7 @@ fuzz_main(const uint8_t *data, size_t sz)
|
|||
char *fuzzing_data = tor_memdup_nulterm(data, sz);
|
||||
memset(subcredential, 'A', sizeof(subcredential));
|
||||
|
||||
hs_desc_decode_descriptor(fuzzing_data, subcredential, &desc);
|
||||
hs_desc_decode_descriptor(fuzzing_data, subcredential, NULL, &desc);
|
||||
if (desc) {
|
||||
log_debug(LD_GENERAL, "Decoding okay");
|
||||
hs_descriptor_free(desc);
|
||||
|
|
|
@ -98,8 +98,11 @@ static hs_descriptor_t *
|
|||
hs_helper_build_hs_desc_impl(unsigned int no_ip,
|
||||
const ed25519_keypair_t *signing_kp)
|
||||
{
|
||||
int ret;
|
||||
int i;
|
||||
time_t now = approx_time();
|
||||
ed25519_keypair_t blinded_kp;
|
||||
curve25519_keypair_t auth_ephemeral_kp;
|
||||
hs_descriptor_t *descp = NULL, *desc = tor_malloc_zero(sizeof(*desc));
|
||||
|
||||
desc->plaintext_data.version = HS_DESC_SUPPORTED_FORMAT_VERSION_MAX;
|
||||
|
@ -126,6 +129,20 @@ hs_helper_build_hs_desc_impl(unsigned int no_ip,
|
|||
hs_get_subcredential(&signing_kp->pubkey, &blinded_kp.pubkey,
|
||||
desc->subcredential);
|
||||
|
||||
/* Setup superencrypted data section. */
|
||||
ret = curve25519_keypair_generate(&auth_ephemeral_kp, 0);
|
||||
tt_int_op(ret, ==, 0);
|
||||
memcpy(&desc->superencrypted_data.auth_ephemeral_pubkey,
|
||||
&auth_ephemeral_kp.pubkey,
|
||||
sizeof(curve25519_public_key_t));
|
||||
|
||||
desc->superencrypted_data.clients = smartlist_new();
|
||||
for (i = 0; i < HS_DESC_AUTH_CLIENT_MULTIPLE; i++) {
|
||||
hs_desc_authorized_client_t *desc_client =
|
||||
hs_desc_build_fake_authorized_client();
|
||||
smartlist_add(desc->superencrypted_data.clients, desc_client);
|
||||
}
|
||||
|
||||
/* Setup encrypted data section. */
|
||||
desc->encrypted_data.create2_ntor = 1;
|
||||
desc->encrypted_data.intro_auth_types = smartlist_new();
|
||||
|
@ -207,6 +224,32 @@ hs_helper_desc_equal(const hs_descriptor_t *desc1,
|
|||
* encrypted blob. As contrast to the decoding process where we populate a
|
||||
* descriptor object. */
|
||||
|
||||
/* Superencrypted data section. */
|
||||
tt_mem_op(desc1->superencrypted_data.auth_ephemeral_pubkey.public_key, OP_EQ,
|
||||
desc2->superencrypted_data.auth_ephemeral_pubkey.public_key,
|
||||
CURVE25519_PUBKEY_LEN);
|
||||
|
||||
/* Auth clients. */
|
||||
{
|
||||
tt_assert(desc1->superencrypted_data.clients);
|
||||
tt_assert(desc2->superencrypted_data.clients);
|
||||
tt_int_op(smartlist_len(desc1->superencrypted_data.clients), ==,
|
||||
smartlist_len(desc2->superencrypted_data.clients));
|
||||
for (int i=0;
|
||||
i < smartlist_len(desc1->superencrypted_data.clients);
|
||||
i++) {
|
||||
hs_desc_authorized_client_t
|
||||
*client1 = smartlist_get(desc1->superencrypted_data.clients, i),
|
||||
*client2 = smartlist_get(desc2->superencrypted_data.clients, i);
|
||||
tor_memeq(client1->client_id, client2->client_id,
|
||||
sizeof(client1->client_id));
|
||||
tor_memeq(client1->iv, client2->iv,
|
||||
sizeof(client1->iv));
|
||||
tor_memeq(client1->encrypted_cookie, client2->encrypted_cookie,
|
||||
sizeof(client1->encrypted_cookie));
|
||||
}
|
||||
}
|
||||
|
||||
/* Encrypted data section. */
|
||||
tt_uint_op(desc1->encrypted_data.create2_ntor, ==,
|
||||
desc2->encrypted_data.create2_ntor);
|
||||
|
|
|
@ -64,7 +64,7 @@ test_directory(void *arg)
|
|||
tt_int_op(ret, OP_EQ, 0);
|
||||
desc1 = hs_helper_build_hs_desc_with_ip(&signing_kp1);
|
||||
tt_assert(desc1);
|
||||
ret = hs_desc_encode_descriptor(desc1, &signing_kp1, &desc1_str);
|
||||
ret = hs_desc_encode_descriptor(desc1, &signing_kp1, NULL, &desc1_str);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
|
||||
/* Very first basic test, should be able to be stored, survive a
|
||||
|
@ -102,7 +102,7 @@ test_directory(void *arg)
|
|||
desc_zero_lifetime->plaintext_data.lifetime_sec = 0;
|
||||
char *desc_zero_lifetime_str;
|
||||
ret = hs_desc_encode_descriptor(desc_zero_lifetime, &signing_kp_zero,
|
||||
&desc_zero_lifetime_str);
|
||||
NULL, &desc_zero_lifetime_str);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
|
||||
ret = hs_cache_store_as_dir(desc1_str);
|
||||
|
@ -153,7 +153,7 @@ test_directory(void *arg)
|
|||
tt_int_op(ret, OP_EQ, 1);
|
||||
/* Bump revision counter. */
|
||||
desc1->plaintext_data.revision_counter++;
|
||||
ret = hs_desc_encode_descriptor(desc1, &signing_kp1, &new_desc_str);
|
||||
ret = hs_desc_encode_descriptor(desc1, &signing_kp1, NULL, &new_desc_str);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
ret = hs_cache_store_as_dir(new_desc_str);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
|
@ -187,7 +187,7 @@ test_clean_as_dir(void *arg)
|
|||
tt_int_op(ret, OP_EQ, 0);
|
||||
desc1 = hs_helper_build_hs_desc_with_ip(&signing_kp1);
|
||||
tt_assert(desc1);
|
||||
ret = hs_desc_encode_descriptor(desc1, &signing_kp1, &desc1_str);
|
||||
ret = hs_desc_encode_descriptor(desc1, &signing_kp1, NULL, &desc1_str);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
ret = hs_cache_store_as_dir(desc1_str);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
|
@ -301,7 +301,7 @@ test_upload_and_download_hs_desc(void *arg)
|
|||
published_desc = hs_helper_build_hs_desc_with_ip(&signing_kp);
|
||||
tt_assert(published_desc);
|
||||
retval = hs_desc_encode_descriptor(published_desc, &signing_kp,
|
||||
&published_desc_str);
|
||||
NULL, &published_desc_str);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
}
|
||||
|
||||
|
@ -365,7 +365,7 @@ test_hsdir_revision_counter_check(void *arg)
|
|||
published_desc = hs_helper_build_hs_desc_with_ip(&signing_kp);
|
||||
tt_assert(published_desc);
|
||||
retval = hs_desc_encode_descriptor(published_desc, &signing_kp,
|
||||
&published_desc_str);
|
||||
NULL, &published_desc_str);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
}
|
||||
|
||||
|
@ -390,7 +390,7 @@ test_hsdir_revision_counter_check(void *arg)
|
|||
received_desc_str = helper_fetch_desc_from_hsdir(blinded_key);
|
||||
|
||||
retval = hs_desc_decode_descriptor(received_desc_str,
|
||||
subcredential, &received_desc);
|
||||
subcredential, NULL, &received_desc);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
tt_assert(received_desc);
|
||||
|
||||
|
@ -407,7 +407,7 @@ test_hsdir_revision_counter_check(void *arg)
|
|||
published_desc->plaintext_data.revision_counter = 1313;
|
||||
tor_free(published_desc_str);
|
||||
retval = hs_desc_encode_descriptor(published_desc, &signing_kp,
|
||||
&published_desc_str);
|
||||
NULL, &published_desc_str);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
|
||||
retval = handle_post_hs_descriptor("/tor/hs/3/publish",published_desc_str);
|
||||
|
@ -423,7 +423,7 @@ test_hsdir_revision_counter_check(void *arg)
|
|||
received_desc_str = helper_fetch_desc_from_hsdir(blinded_key);
|
||||
|
||||
retval = hs_desc_decode_descriptor(received_desc_str,
|
||||
subcredential, &received_desc);
|
||||
subcredential, NULL, &received_desc);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
tt_assert(received_desc);
|
||||
|
||||
|
@ -482,7 +482,7 @@ test_client_cache(void *arg)
|
|||
published_desc = hs_helper_build_hs_desc_with_ip(&signing_kp);
|
||||
tt_assert(published_desc);
|
||||
retval = hs_desc_encode_descriptor(published_desc, &signing_kp,
|
||||
&published_desc_str);
|
||||
NULL, &published_desc_str);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
memcpy(wanted_subcredential, published_desc->subcredential, DIGEST256_LEN);
|
||||
tt_assert(!tor_mem_is_zero((char*)wanted_subcredential, DIGEST256_LEN));
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* \brief Test prop224 HS client functionality.
|
||||
*/
|
||||
|
||||
#define CONFIG_PRIVATE
|
||||
#define CRYPTO_PRIVATE
|
||||
#define MAIN_PRIVATE
|
||||
#define HS_CLIENT_PRIVATE
|
||||
|
@ -32,6 +33,7 @@
|
|||
#include "feature/hs/hs_circuit.h"
|
||||
#include "feature/hs/hs_circuitmap.h"
|
||||
#include "feature/hs/hs_client.h"
|
||||
#include "feature/hs/hs_config.h"
|
||||
#include "feature/hs/hs_ident.h"
|
||||
#include "feature/hs/hs_cache.h"
|
||||
#include "core/or/circuitlist.h"
|
||||
|
@ -73,6 +75,20 @@ mock_networkstatus_get_live_consensus(time_t now)
|
|||
return &mock_ns;
|
||||
}
|
||||
|
||||
static int
|
||||
helper_config_client(const char *conf, int validate_only)
|
||||
{
|
||||
int ret = 0;
|
||||
or_options_t *options = NULL;
|
||||
tt_assert(conf);
|
||||
options = helper_parse_options(conf);
|
||||
tt_assert(options);
|
||||
ret = hs_config_client_auth_all(options, validate_only);
|
||||
done:
|
||||
or_options_free(options);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Test helper function: Setup a circuit and a stream with the same hidden
|
||||
* service destination, and put them in <b>circ_out</b> and
|
||||
* <b>conn_out</b>. Make the stream wait for circuits to be established to the
|
||||
|
@ -366,7 +382,7 @@ test_client_pick_intro(void *arg)
|
|||
{
|
||||
char *encoded = NULL;
|
||||
desc = hs_helper_build_hs_desc_with_ip(&service_kp);
|
||||
ret = hs_desc_encode_descriptor(desc, &service_kp, &encoded);
|
||||
ret = hs_desc_encode_descriptor(desc, &service_kp, NULL, &encoded);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
tt_assert(encoded);
|
||||
|
||||
|
@ -601,6 +617,160 @@ test_descriptor_fetch(void *arg)
|
|||
hs_free_all();
|
||||
}
|
||||
|
||||
static void
|
||||
test_auth_key_filename_is_valid(void *arg)
|
||||
{
|
||||
(void) arg;
|
||||
|
||||
/* Valid file name. */
|
||||
tt_assert(auth_key_filename_is_valid("a.auth_private"));
|
||||
/* Valid file name with special character. */
|
||||
tt_assert(auth_key_filename_is_valid("a-.auth_private"));
|
||||
/* Invalid extension. */
|
||||
tt_assert(!auth_key_filename_is_valid("a.ath_private"));
|
||||
/* Nothing before the extension. */
|
||||
tt_assert(!auth_key_filename_is_valid(".auth_private"));
|
||||
|
||||
done:
|
||||
;
|
||||
}
|
||||
|
||||
static void
|
||||
test_parse_auth_file_content(void *arg)
|
||||
{
|
||||
hs_client_service_authorization_t *auth = NULL;
|
||||
|
||||
(void) arg;
|
||||
|
||||
/* Valid authorized client. */
|
||||
auth = parse_auth_file_content(
|
||||
"4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad:descriptor:"
|
||||
"x25519:zdsyvn2jq534ugyiuzgjy4267jbtzcjbsgedhshzx5mforyxtryq");
|
||||
tt_assert(auth);
|
||||
|
||||
/* Wrong number of fields. */
|
||||
tt_assert(!parse_auth_file_content("a:b"));
|
||||
/* Wrong auth type. */
|
||||
tt_assert(!parse_auth_file_content(
|
||||
"4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad:x:"
|
||||
"x25519:zdsyvn2jq534ugyiuzgjy4267jbtzcjbsgedhshzx5mforyxtryq"));
|
||||
/* Wrong key type. */
|
||||
tt_assert(!parse_auth_file_content(
|
||||
"4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad:descriptor:"
|
||||
"x:zdsyvn2jq534ugyiuzgjy4267jbtzcjbsgedhshzx5mforyxtryq"));
|
||||
/* Some malformed string. */
|
||||
tt_assert(!parse_auth_file_content("xx:descriptor:x25519:aa=="));
|
||||
/* Bigger key than it should be */
|
||||
tt_assert(!parse_auth_file_content("xx:descriptor:x25519:"
|
||||
"vjqea4jbhwwc4hto7ekyvqfbeodghbaq6nxi45hz4wr3qvhqv3yqa"));
|
||||
done:
|
||||
tor_free(auth);
|
||||
}
|
||||
|
||||
static char *
|
||||
mock_read_file_to_str(const char *filename, int flags, struct stat *stat_out)
|
||||
{
|
||||
char *ret = NULL;
|
||||
|
||||
(void) flags;
|
||||
(void) stat_out;
|
||||
|
||||
if (!strcmp(filename, get_fname("auth_keys" PATH_SEPARATOR
|
||||
"client1.auth_private"))) {
|
||||
ret = tor_strdup(
|
||||
"4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad:descriptor:"
|
||||
"x25519:zdsyvn2jq534ugyiuzgjy4267jbtzcjbsgedhshzx5mforyxtryq");
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!strcmp(filename, get_fname("auth_keys" PATH_SEPARATOR "dummy.xxx"))) {
|
||||
ret = tor_strdup(
|
||||
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:descriptor:"
|
||||
"x25519:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!strcmp(filename, get_fname("auth_keys" PATH_SEPARATOR
|
||||
"client2.auth_private"))) {
|
||||
ret = tor_strdup(
|
||||
"25njqamcweflpvkl73j4szahhihoc4xt3ktcgjnpaingr5yhkenl5sid:descriptor:"
|
||||
"x25519:fdreqzjqso7d2ac7qscrxfl5qfpamdvgy5d6cxejcgzc3hvhurmq");
|
||||
goto done;
|
||||
}
|
||||
|
||||
done:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
mock_check_private_dir(const char *dirname, cpd_check_t check,
|
||||
const char *effective_user)
|
||||
{
|
||||
(void) dirname;
|
||||
(void) check;
|
||||
(void) effective_user;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static smartlist_t *
|
||||
mock_tor_listdir(const char *dirname)
|
||||
{
|
||||
smartlist_t *file_list = smartlist_new();
|
||||
|
||||
(void) dirname;
|
||||
|
||||
smartlist_add(file_list, tor_strdup("client1.auth_private"));
|
||||
smartlist_add(file_list, tor_strdup("dummy.xxx"));
|
||||
smartlist_add(file_list, tor_strdup("client2.auth_private"));
|
||||
|
||||
return file_list;
|
||||
}
|
||||
|
||||
static void
|
||||
test_config_client_authorization(void *arg)
|
||||
{
|
||||
int ret;
|
||||
char *conf = NULL;
|
||||
ed25519_public_key_t pk1, pk2;
|
||||
digest256map_t *global_map = NULL;
|
||||
char *key_dir = tor_strdup(get_fname("auth_keys"));
|
||||
|
||||
(void) arg;
|
||||
|
||||
MOCK(read_file_to_str, mock_read_file_to_str);
|
||||
MOCK(tor_listdir, mock_tor_listdir);
|
||||
MOCK(check_private_dir, mock_check_private_dir);
|
||||
|
||||
#define conf_fmt \
|
||||
"ClientOnionAuthDir %s\n"
|
||||
|
||||
tor_asprintf(&conf, conf_fmt, key_dir);
|
||||
ret = helper_config_client(conf, 0);
|
||||
tor_free(conf);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
|
||||
#undef conf_fmt
|
||||
|
||||
global_map = get_hs_client_auths_map();
|
||||
tt_int_op(digest256map_size(global_map), OP_EQ, 2);
|
||||
|
||||
hs_parse_address("4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad",
|
||||
&pk1, NULL, NULL);
|
||||
hs_parse_address("25njqamcweflpvkl73j4szahhihoc4xt3ktcgjnpaingr5yhkenl5sid",
|
||||
&pk2, NULL, NULL);
|
||||
|
||||
tt_assert(digest256map_get(global_map, pk1.pubkey));
|
||||
tt_assert(digest256map_get(global_map, pk2.pubkey));
|
||||
|
||||
done:
|
||||
tor_free(key_dir);
|
||||
hs_free_all();
|
||||
UNMOCK(read_file_to_str);
|
||||
UNMOCK(tor_listdir);
|
||||
UNMOCK(check_private_dir);
|
||||
}
|
||||
|
||||
struct testcase_t hs_client_tests[] = {
|
||||
{ "e2e_rend_circuit_setup_legacy", test_e2e_rend_circuit_setup_legacy,
|
||||
TT_FORK, NULL, NULL },
|
||||
|
@ -610,5 +780,11 @@ struct testcase_t hs_client_tests[] = {
|
|||
TT_FORK, NULL, NULL },
|
||||
{ "descriptor_fetch", test_descriptor_fetch,
|
||||
TT_FORK, NULL, NULL },
|
||||
{ "auth_key_filename_is_valid", test_auth_key_filename_is_valid, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "parse_auth_file_content", test_parse_auth_file_content, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "config_client_authorization", test_config_client_authorization,
|
||||
TT_FORK, NULL, NULL },
|
||||
END_OF_TESTCASES
|
||||
};
|
||||
|
|
|
@ -428,11 +428,13 @@ mock_directory_initiate_request(directory_request_t *req)
|
|||
|
||||
static int
|
||||
mock_hs_desc_encode_descriptor(const hs_descriptor_t *desc,
|
||||
const ed25519_keypair_t *signing_kp,
|
||||
char **encoded_out)
|
||||
const ed25519_keypair_t *signing_kp,
|
||||
const uint8_t *descriptor_cookie,
|
||||
char **encoded_out)
|
||||
{
|
||||
(void)desc;
|
||||
(void)signing_kp;
|
||||
(void)descriptor_cookie;
|
||||
|
||||
tor_asprintf(encoded_out, "lulu");
|
||||
return 0;
|
||||
|
|
|
@ -30,6 +30,13 @@ DISABLE_GCC_WARNING(overlength-strings)
|
|||
#include "test_hs_descriptor.inc"
|
||||
ENABLE_GCC_WARNING(overlength-strings)
|
||||
|
||||
/* Mock function to fill all bytes with 1 */
|
||||
static void
|
||||
mock_crypto_strongest_rand(uint8_t *out, size_t out_len)
|
||||
{
|
||||
memset(out, 1, out_len);
|
||||
}
|
||||
|
||||
/* Test certificate encoding put in a descriptor. */
|
||||
static void
|
||||
test_cert_encoding(void *arg)
|
||||
|
@ -284,7 +291,6 @@ static void
|
|||
test_encode_descriptor(void *arg)
|
||||
{
|
||||
int ret;
|
||||
char *encoded = NULL;
|
||||
ed25519_keypair_t signing_kp;
|
||||
hs_descriptor_t *desc = NULL;
|
||||
|
||||
|
@ -293,19 +299,38 @@ test_encode_descriptor(void *arg)
|
|||
ret = ed25519_keypair_generate(&signing_kp, 0);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
desc = hs_helper_build_hs_desc_with_ip(&signing_kp);
|
||||
ret = hs_desc_encode_descriptor(desc, &signing_kp, &encoded);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
tt_assert(encoded);
|
||||
|
||||
{
|
||||
char *encoded = NULL;
|
||||
ret = hs_desc_encode_descriptor(desc, &signing_kp, NULL, &encoded);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
tt_assert(encoded);
|
||||
|
||||
tor_free(encoded);
|
||||
}
|
||||
|
||||
{
|
||||
char *encoded = NULL;
|
||||
uint8_t descriptor_cookie[HS_DESC_DESCRIPTOR_COOKIE_LEN];
|
||||
|
||||
crypto_strongest_rand(descriptor_cookie, sizeof(descriptor_cookie));
|
||||
|
||||
ret = hs_desc_encode_descriptor(desc, &signing_kp,
|
||||
descriptor_cookie, &encoded);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
tt_assert(encoded);
|
||||
|
||||
tor_free(encoded);
|
||||
}
|
||||
done:
|
||||
hs_descriptor_free(desc);
|
||||
tor_free(encoded);
|
||||
}
|
||||
|
||||
static void
|
||||
test_decode_descriptor(void *arg)
|
||||
{
|
||||
int ret;
|
||||
int i;
|
||||
char *encoded = NULL;
|
||||
ed25519_keypair_t signing_kp;
|
||||
hs_descriptor_t *desc = NULL;
|
||||
|
@ -323,14 +348,15 @@ test_decode_descriptor(void *arg)
|
|||
subcredential);
|
||||
|
||||
/* Give some bad stuff to the decoding function. */
|
||||
ret = hs_desc_decode_descriptor("hladfjlkjadf", subcredential, &decoded);
|
||||
ret = hs_desc_decode_descriptor("hladfjlkjadf", subcredential,
|
||||
NULL, &decoded);
|
||||
tt_int_op(ret, OP_EQ, -1);
|
||||
|
||||
ret = hs_desc_encode_descriptor(desc, &signing_kp, &encoded);
|
||||
ret = hs_desc_encode_descriptor(desc, &signing_kp, NULL, &encoded);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
tt_assert(encoded);
|
||||
|
||||
ret = hs_desc_decode_descriptor(encoded, subcredential, &decoded);
|
||||
ret = hs_desc_decode_descriptor(encoded, subcredential, NULL, &decoded);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
tt_assert(decoded);
|
||||
|
||||
|
@ -346,15 +372,86 @@ test_decode_descriptor(void *arg)
|
|||
desc_no_ip = hs_helper_build_hs_desc_no_ip(&signing_kp_no_ip);
|
||||
tt_assert(desc_no_ip);
|
||||
tor_free(encoded);
|
||||
ret = hs_desc_encode_descriptor(desc_no_ip, &signing_kp_no_ip, &encoded);
|
||||
ret = hs_desc_encode_descriptor(desc_no_ip, &signing_kp_no_ip,
|
||||
NULL, &encoded);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
tt_assert(encoded);
|
||||
hs_descriptor_free(decoded);
|
||||
ret = hs_desc_decode_descriptor(encoded, subcredential, &decoded);
|
||||
ret = hs_desc_decode_descriptor(encoded, subcredential, NULL, &decoded);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
tt_assert(decoded);
|
||||
}
|
||||
|
||||
/* Decode a descriptor with auth clients. */
|
||||
{
|
||||
uint8_t descriptor_cookie[HS_DESC_DESCRIPTOR_COOKIE_LEN];
|
||||
curve25519_keypair_t auth_ephemeral_kp;
|
||||
curve25519_keypair_t client_kp, invalid_client_kp;
|
||||
smartlist_t *clients;
|
||||
hs_desc_authorized_client_t *client, *fake_client;
|
||||
client = tor_malloc_zero(sizeof(hs_desc_authorized_client_t));
|
||||
|
||||
/* Prepare all the keys needed to build the auth client. */
|
||||
curve25519_keypair_generate(&auth_ephemeral_kp, 0);
|
||||
curve25519_keypair_generate(&client_kp, 0);
|
||||
curve25519_keypair_generate(&invalid_client_kp, 0);
|
||||
crypto_strongest_rand(descriptor_cookie, HS_DESC_DESCRIPTOR_COOKIE_LEN);
|
||||
|
||||
memcpy(&desc->superencrypted_data.auth_ephemeral_pubkey,
|
||||
&auth_ephemeral_kp.pubkey, CURVE25519_PUBKEY_LEN);
|
||||
|
||||
hs_helper_get_subcred_from_identity_keypair(&signing_kp,
|
||||
subcredential);
|
||||
|
||||
/* Build and add the auth client to the descriptor. */
|
||||
clients = desc->superencrypted_data.clients;
|
||||
if (!clients) {
|
||||
clients = smartlist_new();
|
||||
}
|
||||
hs_desc_build_authorized_client(subcredential,
|
||||
&client_kp.pubkey,
|
||||
&auth_ephemeral_kp.seckey,
|
||||
descriptor_cookie, client);
|
||||
smartlist_add(clients, client);
|
||||
|
||||
/* We need to add fake auth clients here. */
|
||||
for (i=0; i < 15; ++i) {
|
||||
fake_client = hs_desc_build_fake_authorized_client();
|
||||
smartlist_add(clients, fake_client);
|
||||
}
|
||||
desc->superencrypted_data.clients = clients;
|
||||
|
||||
/* Test the encoding/decoding in the following lines. */
|
||||
tor_free(encoded);
|
||||
ret = hs_desc_encode_descriptor(desc, &signing_kp,
|
||||
descriptor_cookie, &encoded);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
tt_assert(encoded);
|
||||
|
||||
/* If we do not have the client secret key, the decoding must fail. */
|
||||
hs_descriptor_free(decoded);
|
||||
ret = hs_desc_decode_descriptor(encoded, subcredential,
|
||||
NULL, &decoded);
|
||||
tt_int_op(ret, OP_LT, 0);
|
||||
tt_assert(!decoded);
|
||||
|
||||
/* If we have an invalid client secret key, the decoding must fail. */
|
||||
hs_descriptor_free(decoded);
|
||||
ret = hs_desc_decode_descriptor(encoded, subcredential,
|
||||
&invalid_client_kp.seckey, &decoded);
|
||||
tt_int_op(ret, OP_LT, 0);
|
||||
tt_assert(!decoded);
|
||||
|
||||
/* If we have the client secret key, the decoding must succeed and the
|
||||
* decoded descriptor must be correct. */
|
||||
ret = hs_desc_decode_descriptor(encoded, subcredential,
|
||||
&client_kp.seckey, &decoded);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
tt_assert(decoded);
|
||||
|
||||
hs_helper_desc_equal(desc, decoded);
|
||||
}
|
||||
|
||||
done:
|
||||
hs_descriptor_free(desc);
|
||||
hs_descriptor_free(desc_no_ip);
|
||||
|
@ -588,7 +685,7 @@ test_decode_bad_signature(void *arg)
|
|||
teardown_capture_of_logs();
|
||||
|
||||
done:
|
||||
desc_plaintext_data_free_contents(&desc_plaintext);
|
||||
hs_desc_plaintext_data_free_contents(&desc_plaintext);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -764,101 +861,69 @@ test_desc_signature(void *arg)
|
|||
tor_free(data);
|
||||
}
|
||||
|
||||
/* bad desc auth type */
|
||||
static const char bad_superencrypted_text1[] = "desc-auth-type scoobysnack\n"
|
||||
"desc-auth-ephemeral-key A/O8DVtnUheb3r1JqoB8uJB7wxXL1XJX3eny4yB+eFA=\n"
|
||||
"auth-client oiNrQB8WwKo S5D02W7vKgiWIMygrBl8RQ FB//SfOBmLEx1kViEWWL1g\n"
|
||||
"encrypted\n"
|
||||
"-----BEGIN MESSAGE-----\n"
|
||||
"YmVpbmcgb24gbW91bnRhaW5zLCB0aGlua2luZyBhYm91dCBjb21wdXRlcnMsIGlzIG5vdC"
|
||||
"BiYWQgYXQgYWxs\n"
|
||||
"-----END MESSAGE-----\n";
|
||||
|
||||
/* bad ephemeral key */
|
||||
static const char bad_superencrypted_text2[] = "desc-auth-type x25519\n"
|
||||
"desc-auth-ephemeral-key differentalphabet\n"
|
||||
"auth-client oiNrQB8WwKo S5D02W7vKgiWIMygrBl8RQ FB//SfOBmLEx1kViEWWL1g\n"
|
||||
"encrypted\n"
|
||||
"-----BEGIN MESSAGE-----\n"
|
||||
"YmVpbmcgb24gbW91bnRhaW5zLCB0aGlua2luZyBhYm91dCBjb21wdXRlcnMsIGlzIG5vdC"
|
||||
"BiYWQgYXQgYWxs\n"
|
||||
"-----END MESSAGE-----\n";
|
||||
|
||||
/* bad encrypted msg */
|
||||
static const char bad_superencrypted_text3[] = "desc-auth-type x25519\n"
|
||||
"desc-auth-ephemeral-key A/O8DVtnUheb3r1JqoB8uJB7wxXL1XJX3eny4yB+eFA=\n"
|
||||
"auth-client oiNrQB8WwKo S5D02W7vKgiWIMygrBl8RQ FB//SfOBmLEx1kViEWWL1g\n"
|
||||
"encrypted\n"
|
||||
"-----BEGIN MESSAGE-----\n"
|
||||
"SO SMALL NOT GOOD\n"
|
||||
"-----END MESSAGE-----\n";
|
||||
|
||||
static const char correct_superencrypted_text[] = "desc-auth-type x25519\n"
|
||||
"desc-auth-ephemeral-key A/O8DVtnUheb3r1JqoB8uJB7wxXL1XJX3eny4yB+eFA=\n"
|
||||
"auth-client oiNrQB8WwKo S5D02W7vKgiWIMygrBl8RQ FB//SfOBmLEx1kViEWWL1g\n"
|
||||
"auth-client Od09Qu636Qo /PKLzqewAdS/+0+vZC+MvQ dpw4NFo13zDnuPz45rxrOg\n"
|
||||
"auth-client JRr840iGYN0 8s8cxYqF7Lx23+NducC4Qg zAafl4wPLURkuEjJreZq1g\n"
|
||||
"encrypted\n"
|
||||
"-----BEGIN MESSAGE-----\n"
|
||||
"YmVpbmcgb24gbW91bnRhaW5zLCB0aGlua2luZyBhYm91dCBjb21wdXRlcnMsIGlzIG5vdC"
|
||||
"BiYWQgYXQgYWxs\n"
|
||||
"-----END MESSAGE-----\n";
|
||||
|
||||
static const char correct_encrypted_plaintext[] = "being on mountains, "
|
||||
"thinking about computers, is not bad at all";
|
||||
|
||||
static void
|
||||
test_parse_hs_desc_superencrypted(void *arg)
|
||||
test_build_authorized_client(void *arg)
|
||||
{
|
||||
int ret;
|
||||
hs_desc_authorized_client_t *desc_client = NULL;
|
||||
uint8_t descriptor_cookie[HS_DESC_DESCRIPTOR_COOKIE_LEN];
|
||||
curve25519_secret_key_t auth_ephemeral_sk;
|
||||
curve25519_secret_key_t client_auth_sk;
|
||||
curve25519_public_key_t client_auth_pk;
|
||||
const char ephemeral_sk_b16[] =
|
||||
"d023b674d993a5c8446bd2ca97e9961149b3c0e88c7dc14e8777744dd3468d6a";
|
||||
const char descriptor_cookie_b16[] =
|
||||
"07d087f1d8c68393721f6e70316d3b29";
|
||||
const char client_pubkey_b16[] =
|
||||
"8c1298fa6050e372f8598f6deca32e27b0ad457741422c2629ebb132cf7fae37";
|
||||
uint8_t subcredential[DIGEST256_LEN];
|
||||
char *mem_op_hex_tmp=NULL;
|
||||
|
||||
(void) arg;
|
||||
size_t retval;
|
||||
uint8_t *encrypted_out = NULL;
|
||||
|
||||
{
|
||||
setup_full_capture_of_logs(LOG_WARN);
|
||||
retval = decode_superencrypted(bad_superencrypted_text1,
|
||||
strlen(bad_superencrypted_text1),
|
||||
&encrypted_out);
|
||||
tt_u64_op(retval, OP_EQ, 0);
|
||||
tt_ptr_op(encrypted_out, OP_EQ, NULL);
|
||||
expect_log_msg_containing("Unrecognized desc auth type");
|
||||
teardown_capture_of_logs();
|
||||
}
|
||||
ret = curve25519_secret_key_generate(&auth_ephemeral_sk, 0);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
|
||||
{
|
||||
setup_full_capture_of_logs(LOG_WARN);
|
||||
retval = decode_superencrypted(bad_superencrypted_text2,
|
||||
strlen(bad_superencrypted_text2),
|
||||
&encrypted_out);
|
||||
tt_u64_op(retval, OP_EQ, 0);
|
||||
tt_ptr_op(encrypted_out, OP_EQ, NULL);
|
||||
expect_log_msg_containing("Bogus desc auth key in HS desc");
|
||||
teardown_capture_of_logs();
|
||||
}
|
||||
ret = curve25519_secret_key_generate(&client_auth_sk, 0);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
curve25519_public_key_generate(&client_auth_pk, &client_auth_sk);
|
||||
|
||||
{
|
||||
setup_full_capture_of_logs(LOG_WARN);
|
||||
retval = decode_superencrypted(bad_superencrypted_text3,
|
||||
strlen(bad_superencrypted_text3),
|
||||
&encrypted_out);
|
||||
tt_u64_op(retval, OP_EQ, 0);
|
||||
tt_ptr_op(encrypted_out, OP_EQ, NULL);
|
||||
expect_log_msg_containing("Length of descriptor\'s encrypted data "
|
||||
"is too small.");
|
||||
teardown_capture_of_logs();
|
||||
}
|
||||
memset(subcredential, 42, sizeof(subcredential));
|
||||
|
||||
/* Now finally the good one */
|
||||
retval = decode_superencrypted(correct_superencrypted_text,
|
||||
strlen(correct_superencrypted_text),
|
||||
&encrypted_out);
|
||||
desc_client = tor_malloc_zero(sizeof(hs_desc_authorized_client_t));
|
||||
|
||||
tt_u64_op(retval, OP_EQ, strlen(correct_encrypted_plaintext));
|
||||
tt_mem_op(encrypted_out, OP_EQ, correct_encrypted_plaintext,
|
||||
strlen(correct_encrypted_plaintext));
|
||||
base16_decode((char *) &auth_ephemeral_sk,
|
||||
sizeof(auth_ephemeral_sk),
|
||||
ephemeral_sk_b16,
|
||||
strlen(ephemeral_sk_b16));
|
||||
|
||||
base16_decode((char *) descriptor_cookie,
|
||||
sizeof(descriptor_cookie),
|
||||
descriptor_cookie_b16,
|
||||
strlen(descriptor_cookie_b16));
|
||||
|
||||
base16_decode((char *) &client_auth_pk,
|
||||
sizeof(client_auth_pk),
|
||||
client_pubkey_b16,
|
||||
strlen(client_pubkey_b16));
|
||||
|
||||
MOCK(crypto_strongest_rand, mock_crypto_strongest_rand);
|
||||
|
||||
hs_desc_build_authorized_client(subcredential,
|
||||
&client_auth_pk, &auth_ephemeral_sk,
|
||||
descriptor_cookie, desc_client);
|
||||
|
||||
test_memeq_hex((char *) desc_client->client_id,
|
||||
"EC19B7FF4D2DDA13");
|
||||
test_memeq_hex((char *) desc_client->iv,
|
||||
"01010101010101010101010101010101");
|
||||
test_memeq_hex((char *) desc_client->encrypted_cookie,
|
||||
"B21222BE13F385F355BD07B2381F9F29");
|
||||
|
||||
done:
|
||||
tor_free(encrypted_out);
|
||||
tor_free(desc_client);
|
||||
tor_free(mem_op_hex_tmp);
|
||||
UNMOCK(crypto_strongest_rand);
|
||||
}
|
||||
|
||||
struct testcase_t hs_descriptor[] = {
|
||||
|
@ -891,9 +956,8 @@ struct testcase_t hs_descriptor[] = {
|
|||
NULL, NULL },
|
||||
{ "desc_signature", test_desc_signature, TT_FORK,
|
||||
NULL, NULL },
|
||||
|
||||
{ "parse_hs_desc_superencrypted", test_parse_hs_desc_superencrypted,
|
||||
TT_FORK, NULL, NULL },
|
||||
{ "build_authorized_client", test_build_authorized_client, TT_FORK,
|
||||
NULL, NULL },
|
||||
|
||||
END_OF_TESTCASES
|
||||
};
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include "core/or/circuitlist.h"
|
||||
#include "core/or/circuituse.h"
|
||||
#include "lib/crypt_ops/crypto_rand.h"
|
||||
#include "lib/fs/dir.h"
|
||||
#include "feature/dirauth/dirvote.h"
|
||||
#include "feature/nodelist/networkstatus.h"
|
||||
#include "feature/nodelist/nodelist.h"
|
||||
|
@ -65,6 +66,13 @@
|
|||
/* Trunnel */
|
||||
#include "trunnel/hs/cell_establish_intro.h"
|
||||
|
||||
#ifdef HAVE_SYS_STAT_H
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
#ifdef HAVE_UNISTD_H
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
static networkstatus_t mock_ns;
|
||||
|
||||
static networkstatus_t *
|
||||
|
@ -220,6 +228,40 @@ helper_create_origin_circuit(int purpose, int flags)
|
|||
return circ;
|
||||
}
|
||||
|
||||
/* Helper: Return a newly allocated authorized client object with
|
||||
* and a newly generated public key. */
|
||||
static hs_service_authorized_client_t *
|
||||
helper_create_authorized_client(void)
|
||||
{
|
||||
int ret;
|
||||
hs_service_authorized_client_t *client;
|
||||
curve25519_secret_key_t seckey;
|
||||
client = tor_malloc_zero(sizeof(hs_service_authorized_client_t));
|
||||
|
||||
ret = curve25519_secret_key_generate(&seckey, 0);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
curve25519_public_key_generate(&client->client_pk, &seckey);
|
||||
|
||||
done:
|
||||
return client;
|
||||
}
|
||||
|
||||
/* Helper: Return a newly allocated authorized client object with the
|
||||
* same client name and the same public key as the given client. */
|
||||
static hs_service_authorized_client_t *
|
||||
helper_clone_authorized_client(const hs_service_authorized_client_t *client)
|
||||
{
|
||||
hs_service_authorized_client_t *client_out;
|
||||
|
||||
tor_assert(client);
|
||||
|
||||
client_out = tor_malloc_zero(sizeof(hs_service_authorized_client_t));
|
||||
memcpy(client_out->client_pk.public_key,
|
||||
client->client_pk.public_key, CURVE25519_PUBKEY_LEN);
|
||||
|
||||
return client_out;
|
||||
}
|
||||
|
||||
/* Helper: Return a newly allocated service object with the identity keypair
|
||||
* sets and the current descriptor. Then register it to the global map.
|
||||
* Caller should us hs_free_all() to free this service or remove it from the
|
||||
|
@ -244,6 +286,26 @@ helper_create_service(void)
|
|||
return service;
|
||||
}
|
||||
|
||||
/* Helper: Return a newly allocated service object with clients. */
|
||||
static hs_service_t *
|
||||
helper_create_service_with_clients(int num_clients)
|
||||
{
|
||||
int i;
|
||||
hs_service_t *service = helper_create_service();
|
||||
tt_assert(service);
|
||||
service->config.is_client_auth_enabled = 1;
|
||||
service->config.clients = smartlist_new();
|
||||
|
||||
for (i = 0; i < num_clients; i++) {
|
||||
hs_service_authorized_client_t *client;
|
||||
client = helper_create_authorized_client();
|
||||
smartlist_add(service->config.clients, client);
|
||||
}
|
||||
|
||||
done:
|
||||
return service;
|
||||
}
|
||||
|
||||
/* Helper: Return a newly allocated service intro point with two link
|
||||
* specifiers, one IPv4 and one legacy ID set to As. */
|
||||
static hs_service_intro_point_t *
|
||||
|
@ -303,6 +365,8 @@ test_load_keys(void *arg)
|
|||
/* It's in staging? */
|
||||
tt_int_op(get_hs_service_staging_list_size(), OP_EQ, 1);
|
||||
|
||||
#undef conf_fmt
|
||||
|
||||
/* Load the keys for these. After that, the v3 service should be registered
|
||||
* in the global map. */
|
||||
hs_service_load_all_keys();
|
||||
|
@ -322,12 +386,193 @@ test_load_keys(void *arg)
|
|||
tt_int_op(hs_address_is_valid(addr), OP_EQ, 1);
|
||||
tt_str_op(addr, OP_EQ, s->onion_address);
|
||||
|
||||
/* Check that the is_client_auth_enabled is not set. */
|
||||
tt_assert(!s->config.is_client_auth_enabled);
|
||||
|
||||
done:
|
||||
tor_free(hsdir_v2);
|
||||
tor_free(hsdir_v3);
|
||||
hs_free_all();
|
||||
}
|
||||
|
||||
static void
|
||||
test_client_filename_is_valid(void *arg)
|
||||
{
|
||||
(void) arg;
|
||||
|
||||
/* Valid file name. */
|
||||
tt_assert(client_filename_is_valid("a.auth"));
|
||||
/* Valid file name with special character. */
|
||||
tt_assert(client_filename_is_valid("a-.auth"));
|
||||
/* Invalid extension. */
|
||||
tt_assert(!client_filename_is_valid("a.ath"));
|
||||
/* Nothing before the extension. */
|
||||
tt_assert(!client_filename_is_valid(".auth"));
|
||||
|
||||
done:
|
||||
;
|
||||
}
|
||||
|
||||
static void
|
||||
test_parse_authorized_client(void *arg)
|
||||
{
|
||||
hs_service_authorized_client_t *client = NULL;
|
||||
|
||||
(void) arg;
|
||||
|
||||
/* Valid authorized client. */
|
||||
client = parse_authorized_client(
|
||||
"descriptor:x25519:dz4q5xqlb4ldnbs72iarrml4ephk3du4i7o2cgiva5lwr6wkquja");
|
||||
tt_assert(client);
|
||||
|
||||
/* Wrong number of fields. */
|
||||
tt_assert(!parse_authorized_client("a:b:c:d:e"));
|
||||
/* Wrong auth type. */
|
||||
tt_assert(!parse_authorized_client(
|
||||
"x:x25519:dz4q5xqlb4ldnbs72iarrml4ephk3du4i7o2cgiva5lwr6wkquja"));
|
||||
/* Wrong key type. */
|
||||
tt_assert(!parse_authorized_client(
|
||||
"descriptor:x:dz4q5xqlb4ldnbs72iarrml4ephk3du4i7o2cgiva5lwr6wkquja"));
|
||||
/* Some malformed string. */
|
||||
tt_assert(!parse_authorized_client("descriptor:x25519:aa=="));
|
||||
tt_assert(!parse_authorized_client("descriptor:"));
|
||||
tt_assert(!parse_authorized_client("descriptor:x25519"));
|
||||
tt_assert(!parse_authorized_client("descriptor:x25519:"));
|
||||
tt_assert(!parse_authorized_client(""));
|
||||
|
||||
done:
|
||||
service_authorized_client_free(client);
|
||||
}
|
||||
|
||||
static char *
|
||||
mock_read_file_to_str(const char *filename, int flags, struct stat *stat_out)
|
||||
{
|
||||
char *ret = NULL;
|
||||
|
||||
(void) flags;
|
||||
(void) stat_out;
|
||||
|
||||
if (!strcmp(filename, get_fname("hs3" PATH_SEPARATOR
|
||||
"authorized_clients" PATH_SEPARATOR
|
||||
"client1.auth"))) {
|
||||
ret = tor_strdup("descriptor:x25519:"
|
||||
"dz4q5xqlb4ldnbs72iarrml4ephk3du4i7o2cgiva5lwr6wkquja");
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!strcmp(filename, get_fname("hs3" PATH_SEPARATOR
|
||||
"authorized_clients" PATH_SEPARATOR
|
||||
"dummy.xxx"))) {
|
||||
ret = tor_strdup("descriptor:x25519:"
|
||||
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!strcmp(filename, get_fname("hs3" PATH_SEPARATOR
|
||||
"authorized_clients" PATH_SEPARATOR
|
||||
"client2.auth"))) {
|
||||
ret = tor_strdup("descriptor:x25519:"
|
||||
"okoi2gml3wd6x7jganlk5d66xxyjgg24sxw4y7javx4giqr66zta");
|
||||
goto done;
|
||||
}
|
||||
|
||||
done:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static smartlist_t *
|
||||
mock_tor_listdir(const char *dirname)
|
||||
{
|
||||
smartlist_t *file_list = smartlist_new();
|
||||
|
||||
(void) dirname;
|
||||
|
||||
smartlist_add(file_list, tor_strdup("client1.auth"));
|
||||
smartlist_add(file_list, tor_strdup("dummy.xxx"));
|
||||
smartlist_add(file_list, tor_strdup("client2.auth"));
|
||||
|
||||
return file_list;
|
||||
}
|
||||
|
||||
static void
|
||||
test_load_keys_with_client_auth(void *arg)
|
||||
{
|
||||
int ret;
|
||||
char *conf = NULL;
|
||||
smartlist_t *pubkey_b32_list = smartlist_new();
|
||||
char *hsdir_v3 = tor_strdup(get_fname("hs3"));
|
||||
hs_service_t *service;
|
||||
|
||||
(void) arg;
|
||||
|
||||
hs_init();
|
||||
smartlist_add(pubkey_b32_list, tor_strdup(
|
||||
"dz4q5xqlb4ldnbs72iarrml4ephk3du4i7o2cgiva5lwr6wkquja"));
|
||||
smartlist_add(pubkey_b32_list, tor_strdup(
|
||||
"okoi2gml3wd6x7jganlk5d66xxyjgg24sxw4y7javx4giqr66zta"));
|
||||
|
||||
#define conf_fmt \
|
||||
"HiddenServiceDir %s\n" \
|
||||
"HiddenServiceVersion %d\n" \
|
||||
"HiddenServicePort 65534\n"
|
||||
|
||||
tor_asprintf(&conf, conf_fmt, hsdir_v3, HS_VERSION_THREE);
|
||||
ret = helper_config_service(conf);
|
||||
tor_free(conf);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
/* It's in staging? */
|
||||
tt_int_op(get_hs_service_staging_list_size(), OP_EQ, 1);
|
||||
|
||||
#undef conf_fmt
|
||||
|
||||
MOCK(read_file_to_str, mock_read_file_to_str);
|
||||
MOCK(tor_listdir, mock_tor_listdir);
|
||||
|
||||
/* Load the keys for these. After that, the v3 service should be registered
|
||||
* in the global map. */
|
||||
hs_service_load_all_keys();
|
||||
tt_int_op(get_hs_service_map_size(), OP_EQ, 1);
|
||||
|
||||
service = get_first_service();
|
||||
tt_assert(service->config.clients);
|
||||
tt_int_op(smartlist_len(service->config.clients), OP_EQ,
|
||||
smartlist_len(pubkey_b32_list));
|
||||
|
||||
/* Test that the is_client_auth_enabled flag is set. */
|
||||
tt_assert(service->config.is_client_auth_enabled);
|
||||
|
||||
/* Test that the keys in clients are correct. */
|
||||
SMARTLIST_FOREACH_BEGIN(pubkey_b32_list, char *, pubkey_b32) {
|
||||
|
||||
curve25519_public_key_t pubkey;
|
||||
/* This flag will be set if the key is found in clients. */
|
||||
int is_found = 0;
|
||||
base32_decode((char *) pubkey.public_key, sizeof(pubkey.public_key),
|
||||
pubkey_b32, strlen(pubkey_b32));
|
||||
|
||||
SMARTLIST_FOREACH_BEGIN(service->config.clients,
|
||||
hs_service_authorized_client_t *, client) {
|
||||
if (tor_memeq(&pubkey, &client->client_pk, sizeof(pubkey))) {
|
||||
is_found = 1;
|
||||
break;
|
||||
}
|
||||
} SMARTLIST_FOREACH_END(client);
|
||||
|
||||
tt_assert(is_found);
|
||||
|
||||
} SMARTLIST_FOREACH_END(pubkey_b32);
|
||||
|
||||
done:
|
||||
if (pubkey_b32_list) {
|
||||
SMARTLIST_FOREACH(pubkey_b32_list, char *, s, tor_free(s));
|
||||
}
|
||||
smartlist_free(pubkey_b32_list);
|
||||
tor_free(hsdir_v3);
|
||||
hs_free_all();
|
||||
UNMOCK(read_file_to_str);
|
||||
UNMOCK(tor_listdir);
|
||||
}
|
||||
|
||||
static void
|
||||
test_access_service(void *arg)
|
||||
{
|
||||
|
@ -1371,6 +1616,90 @@ test_build_update_descriptors(void *arg)
|
|||
nodelist_free_all();
|
||||
}
|
||||
|
||||
/** Test building descriptors. We use this separate function instead of
|
||||
* using test_build_update_descriptors because that function is too complex
|
||||
* and also too interactive. */
|
||||
static void
|
||||
test_build_descriptors(void *arg)
|
||||
{
|
||||
int ret;
|
||||
time_t now = time(NULL);
|
||||
|
||||
(void) arg;
|
||||
|
||||
hs_init();
|
||||
|
||||
MOCK(get_or_state,
|
||||
get_or_state_replacement);
|
||||
MOCK(networkstatus_get_live_consensus,
|
||||
mock_networkstatus_get_live_consensus);
|
||||
|
||||
dummy_state = tor_malloc_zero(sizeof(or_state_t));
|
||||
|
||||
ret = parse_rfc1123_time("Sat, 26 Oct 1985 03:00:00 UTC",
|
||||
&mock_ns.valid_after);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
ret = parse_rfc1123_time("Sat, 26 Oct 1985 04:00:00 UTC",
|
||||
&mock_ns.fresh_until);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
voting_schedule_recalculate_timing(get_options(), mock_ns.valid_after);
|
||||
|
||||
/* Generate a valid number of fake auth clients when a client authorization
|
||||
* is disabled. */
|
||||
{
|
||||
hs_service_t *service = helper_create_service();
|
||||
service_descriptor_free(service->desc_current);
|
||||
service->desc_current = NULL;
|
||||
|
||||
build_all_descriptors(now);
|
||||
hs_desc_superencrypted_data_t *superencrypted;
|
||||
superencrypted = &service->desc_current->desc->superencrypted_data;
|
||||
tt_int_op(smartlist_len(superencrypted->clients), OP_EQ, 16);
|
||||
}
|
||||
|
||||
/* Generate a valid number of fake auth clients when the number of
|
||||
* clients is zero. */
|
||||
{
|
||||
hs_service_t *service = helper_create_service_with_clients(0);
|
||||
service_descriptor_free(service->desc_current);
|
||||
service->desc_current = NULL;
|
||||
|
||||
build_all_descriptors(now);
|
||||
hs_desc_superencrypted_data_t *superencrypted;
|
||||
superencrypted = &service->desc_current->desc->superencrypted_data;
|
||||
tt_int_op(smartlist_len(superencrypted->clients), OP_EQ, 16);
|
||||
}
|
||||
|
||||
/* Generate a valid number of fake auth clients when the number of
|
||||
* clients is not a multiple of 16. */
|
||||
{
|
||||
hs_service_t *service = helper_create_service_with_clients(20);
|
||||
service_descriptor_free(service->desc_current);
|
||||
service->desc_current = NULL;
|
||||
|
||||
build_all_descriptors(now);
|
||||
hs_desc_superencrypted_data_t *superencrypted;
|
||||
superencrypted = &service->desc_current->desc->superencrypted_data;
|
||||
tt_int_op(smartlist_len(superencrypted->clients), OP_EQ, 32);
|
||||
}
|
||||
|
||||
/* Do not generate any fake desc client when the number of clients is
|
||||
* a multiple of 16 but not zero. */
|
||||
{
|
||||
hs_service_t *service = helper_create_service_with_clients(32);
|
||||
service_descriptor_free(service->desc_current);
|
||||
service->desc_current = NULL;
|
||||
|
||||
build_all_descriptors(now);
|
||||
hs_desc_superencrypted_data_t *superencrypted;
|
||||
superencrypted = &service->desc_current->desc->superencrypted_data;
|
||||
tt_int_op(smartlist_len(superencrypted->clients), OP_EQ, 32);
|
||||
}
|
||||
|
||||
done:
|
||||
hs_free_all();
|
||||
}
|
||||
|
||||
static void
|
||||
test_upload_descriptors(void *arg)
|
||||
{
|
||||
|
@ -1556,11 +1885,137 @@ test_rendezvous1_parsing(void *arg)
|
|||
UNMOCK(relay_send_command_from_edge_);
|
||||
}
|
||||
|
||||
static void
|
||||
test_authorized_client_config_equal(void *arg)
|
||||
{
|
||||
int ret;
|
||||
hs_service_config_t *config1, *config2;
|
||||
|
||||
(void) arg;
|
||||
|
||||
config1 = tor_malloc_zero(sizeof(*config1));
|
||||
config2 = tor_malloc_zero(sizeof(*config2));
|
||||
|
||||
/* Both configs are empty. */
|
||||
{
|
||||
config1->clients = smartlist_new();
|
||||
config2->clients = smartlist_new();
|
||||
|
||||
ret = service_authorized_client_config_equal(config1, config2);
|
||||
tt_int_op(ret, OP_EQ, 1);
|
||||
|
||||
service_clear_config(config1);
|
||||
service_clear_config(config2);
|
||||
}
|
||||
|
||||
/* Both configs have exactly the same client config. */
|
||||
{
|
||||
config1->clients = smartlist_new();
|
||||
config2->clients = smartlist_new();
|
||||
|
||||
hs_service_authorized_client_t *client1, *client2;
|
||||
client1 = helper_create_authorized_client();
|
||||
client2 = helper_create_authorized_client();
|
||||
|
||||
smartlist_add(config1->clients, client1);
|
||||
smartlist_add(config1->clients, client2);
|
||||
|
||||
/* We should swap the order of clients here to test that the order
|
||||
* does not matter. */
|
||||
smartlist_add(config2->clients, helper_clone_authorized_client(client2));
|
||||
smartlist_add(config2->clients, helper_clone_authorized_client(client1));
|
||||
|
||||
ret = service_authorized_client_config_equal(config1, config2);
|
||||
tt_int_op(ret, OP_EQ, 1);
|
||||
|
||||
service_clear_config(config1);
|
||||
service_clear_config(config2);
|
||||
}
|
||||
|
||||
/* The numbers of clients in both configs are not equal. */
|
||||
{
|
||||
config1->clients = smartlist_new();
|
||||
config2->clients = smartlist_new();
|
||||
|
||||
hs_service_authorized_client_t *client1, *client2;
|
||||
client1 = helper_create_authorized_client();
|
||||
client2 = helper_create_authorized_client();
|
||||
|
||||
smartlist_add(config1->clients, client1);
|
||||
smartlist_add(config1->clients, client2);
|
||||
|
||||
smartlist_add(config2->clients, helper_clone_authorized_client(client1));
|
||||
|
||||
ret = service_authorized_client_config_equal(config1, config2);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
|
||||
service_clear_config(config1);
|
||||
service_clear_config(config2);
|
||||
}
|
||||
|
||||
/* The first config has two distinct clients while the second config
|
||||
* has two clients but they are duplicate. */
|
||||
{
|
||||
config1->clients = smartlist_new();
|
||||
config2->clients = smartlist_new();
|
||||
|
||||
hs_service_authorized_client_t *client1, *client2;
|
||||
client1 = helper_create_authorized_client();
|
||||
client2 = helper_create_authorized_client();
|
||||
|
||||
smartlist_add(config1->clients, client1);
|
||||
smartlist_add(config1->clients, client2);
|
||||
|
||||
smartlist_add(config2->clients, helper_clone_authorized_client(client1));
|
||||
smartlist_add(config2->clients, helper_clone_authorized_client(client1));
|
||||
|
||||
ret = service_authorized_client_config_equal(config1, config2);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
|
||||
service_clear_config(config1);
|
||||
service_clear_config(config2);
|
||||
}
|
||||
|
||||
/* Both configs have totally distinct clients. */
|
||||
{
|
||||
config1->clients = smartlist_new();
|
||||
config2->clients = smartlist_new();
|
||||
|
||||
hs_service_authorized_client_t *client1, *client2, *client3, *client4;
|
||||
client1 = helper_create_authorized_client();
|
||||
client2 = helper_create_authorized_client();
|
||||
client3 = helper_create_authorized_client();
|
||||
client4 = helper_create_authorized_client();
|
||||
|
||||
smartlist_add(config1->clients, client1);
|
||||
smartlist_add(config1->clients, client2);
|
||||
|
||||
smartlist_add(config2->clients, client3);
|
||||
smartlist_add(config2->clients, client4);
|
||||
|
||||
ret = service_authorized_client_config_equal(config1, config2);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
|
||||
service_clear_config(config1);
|
||||
service_clear_config(config2);
|
||||
}
|
||||
|
||||
done:
|
||||
tor_free(config1);
|
||||
tor_free(config2);
|
||||
}
|
||||
|
||||
struct testcase_t hs_service_tests[] = {
|
||||
{ "e2e_rend_circuit_setup", test_e2e_rend_circuit_setup, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "load_keys", test_load_keys, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "client_filename_is_valid", test_client_filename_is_valid, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "parse_authorized_client", test_parse_authorized_client, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "load_keys_with_client_auth", test_load_keys_with_client_auth, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "access_service", test_access_service, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "service_intro_point", test_service_intro_point, TT_FORK,
|
||||
|
@ -1583,10 +2038,14 @@ struct testcase_t hs_service_tests[] = {
|
|||
NULL, NULL },
|
||||
{ "build_update_descriptors", test_build_update_descriptors, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "build_descriptors", test_build_descriptors, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "upload_descriptors", test_upload_descriptors, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "rendezvous1_parsing", test_rendezvous1_parsing, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "authorized_client_config_equal", test_authorized_client_config_equal,
|
||||
TT_FORK, NULL, NULL },
|
||||
|
||||
END_OF_TESTCASES
|
||||
};
|
||||
|
|
|
@ -113,8 +113,8 @@ get_fname_suffix(const char *name, const char *suffix)
|
|||
setup_directory();
|
||||
if (!name)
|
||||
return temp_dir;
|
||||
tor_snprintf(buf,sizeof(buf),"%s/%s%s%s",temp_dir,name,suffix ? "_" : "",
|
||||
suffix ? suffix : "");
|
||||
tor_snprintf(buf,sizeof(buf),"%s%s%s%s%s", temp_dir, PATH_SEPARATOR, name,
|
||||
suffix ? "_" : "", suffix ? suffix : "");
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue