summaryrefslogtreecommitdiff
path: root/src/feature
diff options
context:
space:
mode:
authorNick Mathewson <nickm@torproject.org>2018-09-07 15:03:32 -0400
committerNick Mathewson <nickm@torproject.org>2018-09-07 15:03:32 -0400
commit9ca1af9a8757ee73cf4018263cb3ae2340ebaa9f (patch)
treec747d03a54dde4ad4defa2c4c129545aa31064b1 /src/feature
parent13d0855a893a46a0f6dc06dc7d983ea321f7206a (diff)
parent3695ef6343fa1c05cd15a3ddf35c3fe6991ff2ad (diff)
downloadtor-9ca1af9a8757ee73cf4018263cb3ae2340ebaa9f.tar.gz
tor-9ca1af9a8757ee73cf4018263cb3ae2340ebaa9f.zip
Merge remote-tracking branch 'dgoulet/ticket20700_035_03'
Diffstat (limited to 'src/feature')
-rw-r--r--src/feature/hs/hs_client.c266
-rw-r--r--src/feature/hs/hs_client.h24
-rw-r--r--src/feature/hs/hs_config.c27
-rw-r--r--src/feature/hs/hs_config.h1
-rw-r--r--src/feature/hs/hs_descriptor.c1046
-rw-r--r--src/feature/hs/hs_descriptor.h84
-rw-r--r--src/feature/hs/hs_service.c525
-rw-r--r--src/feature/hs/hs_service.h36
8 files changed, 1689 insertions, 320 deletions
diff --git a/src/feature/hs/hs_client.c b/src/feature/hs/hs_client.c
index 1f9218e15a..6f031eb3b9 100644
--- a/src/feature/hs/hs_client.c
+++ b/src/feature/hs/hs_client.c
@@ -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) */
diff --git a/src/feature/hs/hs_client.h b/src/feature/hs/hs_client.h
index 6ee9f40c00..1ba0338dc3 100644
--- a/src/feature/hs/hs_client.h
+++ b/src/feature/hs/hs_client.h
@@ -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) */
diff --git a/src/feature/hs/hs_config.c b/src/feature/hs/hs_config.c
index e972576482..eaeb58829a 100644
--- a/src/feature/hs/hs_config.c
+++ b/src/feature/hs/hs_config.c
@@ -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;
+}
diff --git a/src/feature/hs/hs_config.h b/src/feature/hs/hs_config.h
index 96eb19ee70..f443e814c4 100644
--- a/src/feature/hs/hs_config.h
+++ b/src/feature/hs/hs_config.h
@@ -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) */
diff --git a/src/feature/hs/hs_descriptor.c b/src/feature/hs/hs_descriptor.c
index 3928000164..d0cdffdf10 100644
--- a/src/feature/hs/hs_descriptor.c
+++ b/src/feature/hs/hs_descriptor.c
@@ -152,42 +152,6 @@ static token_rule_t hs_desc_intro_point_v3_token_table[] = {
END_OF_TABLE
};
-/* Free the content of the plaintext section of a descriptor. */
-STATIC void
-desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc)
-{
- if (!desc) {
- return;
- }
-
- if (desc->superencrypted_blob) {
- tor_free(desc->superencrypted_blob);
- }
- tor_cert_free(desc->signing_key_cert);
-
- memwipe(desc, 0, sizeof(*desc));
-}
-
-/* Free the content of the encrypted section of a descriptor. */
-static void
-desc_encrypted_data_free_contents(hs_desc_encrypted_data_t *desc)
-{
- if (!desc) {
- return;
- }
-
- if (desc->intro_auth_types) {
- SMARTLIST_FOREACH(desc->intro_auth_types, char *, a, tor_free(a));
- smartlist_free(desc->intro_auth_types);
- }
- if (desc->intro_points) {
- SMARTLIST_FOREACH(desc->intro_points, hs_desc_intro_point_t *, ip,
- hs_desc_intro_point_free(ip));
- smartlist_free(desc->intro_points);
- }
- memwipe(desc, 0, sizeof(*desc));
-}
-
/* Using a key, salt and encrypted payload, build a MAC and put it in mac_out.
* We use SHA3-256 for the MAC computation.
* This function can't fail. */
@@ -220,53 +184,72 @@ build_mac(const uint8_t *mac_key, size_t mac_key_len,
crypto_digest_free(digest);
}
-/* Using a given decriptor object, build the secret input needed for the
- * KDF and put it in the dst pointer which is an already allocated buffer
- * of size dstlen. */
-static void
-build_secret_input(const hs_descriptor_t *desc, uint8_t *dst, size_t dstlen)
+/* Using a secret data and a given decriptor object, build the secret
+ * input needed for the KDF.
+ *
+ * secret_input = SECRET_DATA | subcredential | INT_8(revision_counter)
+ *
+ * Then, set the newly allocated buffer in secret_input_out and return the
+ * length of the buffer. */
+static size_t
+build_secret_input(const hs_descriptor_t *desc,
+ const uint8_t *secret_data,
+ size_t secret_data_len,
+ uint8_t **secret_input_out)
{
size_t offset = 0;
+ size_t secret_input_len = secret_data_len + DIGEST256_LEN + sizeof(uint64_t);
+ uint8_t *secret_input = NULL;
tor_assert(desc);
- tor_assert(dst);
- tor_assert(HS_DESC_ENCRYPTED_SECRET_INPUT_LEN <= dstlen);
-
- /* XXX use the destination length as the memcpy length */
- /* Copy blinded public key. */
- memcpy(dst, desc->plaintext_data.blinded_pubkey.pubkey,
- sizeof(desc->plaintext_data.blinded_pubkey.pubkey));
- offset += sizeof(desc->plaintext_data.blinded_pubkey.pubkey);
+ tor_assert(secret_data);
+ tor_assert(secret_input_out);
+
+ secret_input = tor_malloc_zero(secret_input_len);
+
+ /* Copy the secret data. */
+ memcpy(secret_input, secret_data, secret_data_len);
+ offset += secret_data_len;
/* Copy subcredential. */
- memcpy(dst + offset, desc->subcredential, sizeof(desc->subcredential));
- offset += sizeof(desc->subcredential);
+ memcpy(secret_input + offset, desc->subcredential, DIGEST256_LEN);
+ offset += DIGEST256_LEN;
/* Copy revision counter value. */
- set_uint64(dst + offset, tor_htonll(desc->plaintext_data.revision_counter));
+ set_uint64(secret_input + offset,
+ tor_htonll(desc->plaintext_data.revision_counter));
offset += sizeof(uint64_t);
- tor_assert(HS_DESC_ENCRYPTED_SECRET_INPUT_LEN == offset);
+ tor_assert(secret_input_len == offset);
+
+ *secret_input_out = secret_input;
+
+ return secret_input_len;
}
/* Do the KDF construction and put the resulting data in key_out which is of
* key_out_len length. It uses SHAKE-256 as specified in the spec. */
static void
build_kdf_key(const hs_descriptor_t *desc,
+ const uint8_t *secret_data,
+ size_t secret_data_len,
const uint8_t *salt, size_t salt_len,
uint8_t *key_out, size_t key_out_len,
int is_superencrypted_layer)
{
- uint8_t secret_input[HS_DESC_ENCRYPTED_SECRET_INPUT_LEN];
+ uint8_t *secret_input = NULL;
+ size_t secret_input_len;
crypto_xof_t *xof;
tor_assert(desc);
+ tor_assert(secret_data);
tor_assert(salt);
tor_assert(key_out);
/* Build the secret input for the KDF computation. */
- build_secret_input(desc, secret_input, sizeof(secret_input));
+ secret_input_len = build_secret_input(desc, secret_data,
+ secret_data_len, &secret_input);
xof = crypto_xof_new();
/* Feed our KDF. [SHAKE it like a polaroid picture --Yawning]. */
- crypto_xof_add_bytes(xof, secret_input, sizeof(secret_input));
+ crypto_xof_add_bytes(xof, secret_input, secret_input_len);
crypto_xof_add_bytes(xof, salt, salt_len);
/* Feed in the right string constant based on the desc layer */
@@ -281,14 +264,18 @@ build_kdf_key(const hs_descriptor_t *desc,
/* Eat from our KDF. */
crypto_xof_squeeze_bytes(xof, key_out, key_out_len);
crypto_xof_free(xof);
- memwipe(secret_input, 0, sizeof(secret_input));
+ memwipe(secret_input, 0, secret_input_len);
+
+ tor_free(secret_input);
}
-/* Using the given descriptor and salt, run it through our KDF function and
- * then extract a secret key in key_out, the IV in iv_out and MAC in mac_out.
- * This function can't fail. */
+/* Using the given descriptor, secret data, and salt, run it through our
+ * KDF function and then extract a secret key in key_out, the IV in iv_out
+ * and MAC in mac_out. This function can't fail. */
static void
build_secret_key_iv_mac(const hs_descriptor_t *desc,
+ const uint8_t *secret_data,
+ size_t secret_data_len,
const uint8_t *salt, size_t salt_len,
uint8_t *key_out, size_t key_len,
uint8_t *iv_out, size_t iv_len,
@@ -299,12 +286,14 @@ build_secret_key_iv_mac(const hs_descriptor_t *desc,
uint8_t kdf_key[HS_DESC_ENCRYPTED_KDF_OUTPUT_LEN];
tor_assert(desc);
+ tor_assert(secret_data);
tor_assert(salt);
tor_assert(key_out);
tor_assert(iv_out);
tor_assert(mac_out);
- build_kdf_key(desc, salt, salt_len, kdf_key, sizeof(kdf_key),
+ build_kdf_key(desc, secret_data, secret_data_len,
+ salt, salt_len, kdf_key, sizeof(kdf_key),
is_superencrypted_layer);
/* Copy the bytes we need for both the secret key and IV. */
memcpy(key_out, kdf_key, key_len);
@@ -610,12 +599,15 @@ build_encrypted(const uint8_t *key, const uint8_t *iv, const char *plaintext,
return encrypted_len;
}
-/* Encrypt the given <b>plaintext</b> buffer using <b>desc</b> to get the
- * keys. Set encrypted_out with the encrypted data and return the length of
- * it. <b>is_superencrypted_layer</b> is set if this is the outer encrypted
- * layer of the descriptor. */
+/* Encrypt the given <b>plaintext</b> buffer using <b>desc</b> and
+ * <b>secret_data</b> to get the keys. Set encrypted_out with the encrypted
+ * data and return the length of it. <b>is_superencrypted_layer</b> is set
+ * if this is the outer encrypted layer of the descriptor. */
static size_t
-encrypt_descriptor_data(const hs_descriptor_t *desc, const char *plaintext,
+encrypt_descriptor_data(const hs_descriptor_t *desc,
+ const uint8_t *secret_data,
+ size_t secret_data_len,
+ const char *plaintext,
char **encrypted_out, int is_superencrypted_layer)
{
char *final_blob;
@@ -626,6 +618,7 @@ encrypt_descriptor_data(const hs_descriptor_t *desc, const char *plaintext,
uint8_t mac_key[DIGEST256_LEN], mac[DIGEST256_LEN];
tor_assert(desc);
+ tor_assert(secret_data);
tor_assert(plaintext);
tor_assert(encrypted_out);
@@ -634,7 +627,8 @@ encrypt_descriptor_data(const hs_descriptor_t *desc, const char *plaintext,
/* KDF construction resulting in a key from which the secret key, IV and MAC
* key are extracted which is what we need for the encryption. */
- build_secret_key_iv_mac(desc, salt, sizeof(salt),
+ build_secret_key_iv_mac(desc, secret_data, secret_data_len,
+ salt, sizeof(salt),
secret_key, sizeof(secret_key),
secret_iv, sizeof(secret_iv),
mac_key, sizeof(mac_key),
@@ -675,69 +669,65 @@ encrypt_descriptor_data(const hs_descriptor_t *desc, const char *plaintext,
return final_blob_len;
}
-/* Create and return a string containing a fake client-auth entry. It's the
- * responsibility of the caller to free the returned string. This function will
- * never fail. */
+/* Create and return a string containing a client-auth entry. It's the
+ * responsibility of the caller to free the returned string. This function
+ * will never fail. */
static char *
-get_fake_auth_client_str(void)
+get_auth_client_str(const hs_desc_authorized_client_t *client)
{
+ int ret;
char *auth_client_str = NULL;
- /* We are gonna fill these arrays with fake base64 data. They are all double
+ /* We are gonna fill these arrays with base64 data. They are all double
* the size of their binary representation to fit the base64 overhead. */
- char client_id_b64[8*2];
- char iv_b64[16*2];
- char encrypted_cookie_b64[16*2];
- int retval;
-
- /* This is a macro to fill a field with random data and then base64 it. */
-#define FILL_WITH_FAKE_DATA_AND_BASE64(field) STMT_BEGIN \
- crypto_rand((char *)field, sizeof(field)); \
- retval = base64_encode_nopad(field##_b64, sizeof(field##_b64), \
- field, sizeof(field)); \
- tor_assert(retval > 0); \
+ char client_id_b64[HS_DESC_CLIENT_ID_LEN * 2];
+ char iv_b64[CIPHER_IV_LEN * 2];
+ char encrypted_cookie_b64[HS_DESC_ENCRYPED_COOKIE_LEN * 2];
+
+#define ASSERT_AND_BASE64(field) STMT_BEGIN \
+ tor_assert(!tor_mem_is_zero((char *) client->field, \
+ sizeof(client->field))); \
+ ret = base64_encode_nopad(field##_b64, sizeof(field##_b64), \
+ client->field, sizeof(client->field)); \
+ tor_assert(ret > 0); \
STMT_END
- { /* Get those fakes! */
- uint8_t client_id[8]; /* fake client-id */
- uint8_t iv[16]; /* fake IV (initialization vector) */
- uint8_t encrypted_cookie[16]; /* fake encrypted cookie */
-
- FILL_WITH_FAKE_DATA_AND_BASE64(client_id);
- FILL_WITH_FAKE_DATA_AND_BASE64(iv);
- FILL_WITH_FAKE_DATA_AND_BASE64(encrypted_cookie);
- }
+ ASSERT_AND_BASE64(client_id);
+ ASSERT_AND_BASE64(iv);
+ ASSERT_AND_BASE64(encrypted_cookie);
/* Build the final string */
tor_asprintf(&auth_client_str, "%s %s %s %s", str_desc_auth_client,
client_id_b64, iv_b64, encrypted_cookie_b64);
-#undef FILL_WITH_FAKE_DATA_AND_BASE64
+#undef ASSERT_AND_BASE64
return auth_client_str;
}
-/** How many lines of "client-auth" we want in our descriptors; fake or not. */
-#define CLIENT_AUTH_ENTRIES_BLOCK_SIZE 16
-
/** Create the "client-auth" part of the descriptor and return a
* newly-allocated string with it. It's the responsibility of the caller to
* free the returned string. */
static char *
-get_fake_auth_client_lines(void)
+get_all_auth_client_lines(const hs_descriptor_t *desc)
{
- /* XXX: Client authorization is still not implemented, so all this function
- does is make fake clients */
- int i = 0;
smartlist_t *auth_client_lines = smartlist_new();
char *auth_client_lines_str = NULL;
- /* Make a line for each fake client */
- const int num_fake_clients = CLIENT_AUTH_ENTRIES_BLOCK_SIZE;
- for (i = 0; i < num_fake_clients; i++) {
- char *auth_client_str = get_fake_auth_client_str();
- tor_assert(auth_client_str);
+ tor_assert(desc);
+ tor_assert(desc->superencrypted_data.clients);
+ tor_assert(smartlist_len(desc->superencrypted_data.clients) != 0);
+ tor_assert(smartlist_len(desc->superencrypted_data.clients)
+ % HS_DESC_AUTH_CLIENT_MULTIPLE == 0);
+
+ /* Make a line for each client */
+ SMARTLIST_FOREACH_BEGIN(desc->superencrypted_data.clients,
+ const hs_desc_authorized_client_t *, client) {
+ char *auth_client_str = NULL;
+
+ auth_client_str = get_auth_client_str(client);
+
smartlist_add(auth_client_lines, auth_client_str);
- }
+ } SMARTLIST_FOREACH_END(client);
/* Join all lines together to form final string */
auth_client_lines_str = smartlist_join_strings(auth_client_lines,
@@ -817,32 +807,29 @@ get_outer_encrypted_layer_plaintext(const hs_descriptor_t *desc,
char *layer1_str = NULL;
smartlist_t *lines = smartlist_new();
- /* XXX: Disclaimer: This function generates only _fake_ client auth
- * data. Real client auth is not yet implemented, but client auth data MUST
- * always be present in descriptors. In the future this function will be
- * refactored to use real client auth data if they exist (#20700). */
- (void) *desc;
-
/* Specify auth type */
smartlist_add_asprintf(lines, "%s %s\n", str_desc_auth_type, "x25519");
- { /* Create fake ephemeral x25519 key */
- char fake_key_base64[CURVE25519_BASE64_PADDED_LEN + 1];
- curve25519_keypair_t fake_x25519_keypair;
- if (curve25519_keypair_generate(&fake_x25519_keypair, 0) < 0) {
- goto done;
- }
- if (curve25519_public_to_base64(fake_key_base64,
- &fake_x25519_keypair.pubkey) < 0) {
+ { /* Print ephemeral x25519 key */
+ char ephemeral_key_base64[CURVE25519_BASE64_PADDED_LEN + 1];
+ const curve25519_public_key_t *ephemeral_pubkey;
+
+ ephemeral_pubkey = &desc->superencrypted_data.auth_ephemeral_pubkey;
+ tor_assert(!tor_mem_is_zero((char *) ephemeral_pubkey->public_key,
+ CURVE25519_PUBKEY_LEN));
+
+ if (curve25519_public_to_base64(ephemeral_key_base64,
+ ephemeral_pubkey) < 0) {
goto done;
}
smartlist_add_asprintf(lines, "%s %s\n",
- str_desc_auth_key, fake_key_base64);
- /* No need to memwipe any of these fake keys. They will go unused. */
+ str_desc_auth_key, ephemeral_key_base64);
+
+ memwipe(ephemeral_key_base64, 0, sizeof(ephemeral_key_base64));
}
- { /* Create fake auth-client lines. */
- char *auth_client_lines = get_fake_auth_client_lines();
+ { /* Create auth-client lines. */
+ char *auth_client_lines = get_all_auth_client_lines(desc);
tor_assert(auth_client_lines);
smartlist_add(lines, auth_client_lines);
}
@@ -860,6 +847,8 @@ get_outer_encrypted_layer_plaintext(const hs_descriptor_t *desc,
layer1_str = smartlist_join_strings(lines, "", 0, NULL);
done:
+ /* We need to memwipe all lines because it contains the ephemeral key */
+ SMARTLIST_FOREACH(lines, char *, a, memwipe(a, 0, strlen(a)));
SMARTLIST_FOREACH(lines, char *, a, tor_free(a));
smartlist_free(lines);
@@ -868,11 +857,14 @@ get_outer_encrypted_layer_plaintext(const hs_descriptor_t *desc,
/* Encrypt <b>encoded_str</b> into an encrypted blob and then base64 it before
* returning it. <b>desc</b> is provided to derive the encryption
- * keys. <b>is_superencrypted_layer</b> is set if <b>encoded_str</b> is the
+ * keys. <b>secret_data</b> is also proved to derive the encryption keys.
+ * <b>is_superencrypted_layer</b> is set if <b>encoded_str</b> is the
* middle (superencrypted) layer of the descriptor. It's the responsibility of
* the caller to free the returned string. */
static char *
encrypt_desc_data_and_base64(const hs_descriptor_t *desc,
+ const uint8_t *secret_data,
+ size_t secret_data_len,
const char *encoded_str,
int is_superencrypted_layer)
{
@@ -880,7 +872,8 @@ encrypt_desc_data_and_base64(const hs_descriptor_t *desc,
ssize_t enc_b64_len, ret_len, enc_len;
char *encrypted_blob = NULL;
- enc_len = encrypt_descriptor_data(desc, encoded_str, &encrypted_blob,
+ enc_len = encrypt_descriptor_data(desc, secret_data, secret_data_len,
+ encoded_str, &encrypted_blob,
is_superencrypted_layer);
/* Get the encoded size plus a NUL terminating byte. */
enc_b64_len = base64_encode_size(enc_len, BASE64_ENCODE_MULTILINE) + 1;
@@ -895,6 +888,53 @@ encrypt_desc_data_and_base64(const hs_descriptor_t *desc,
return enc_b64;
}
+/* Generate the secret data which is used to encrypt/decrypt the descriptor.
+ *
+ * SECRET_DATA = blinded-public-key
+ * SECRET_DATA = blinded-public-key | descriptor_cookie
+ *
+ * The descriptor_cookie is optional but if it exists, it must be at least
+ * HS_DESC_DESCRIPTOR_COOKIE_LEN bytes long.
+ *
+ * A newly allocated secret data is put in secret_data_out. Return the
+ * length of the secret data. This function cannot fail. */
+static size_t
+build_secret_data(const ed25519_public_key_t *blinded_pubkey,
+ const uint8_t *descriptor_cookie,
+ uint8_t **secret_data_out)
+{
+ size_t secret_data_len;
+ uint8_t *secret_data;
+
+ tor_assert(blinded_pubkey);
+ tor_assert(secret_data_out);
+
+ if (descriptor_cookie) {
+ /* If the descriptor cookie is present, we need both the blinded
+ * pubkey and the descriptor cookie as a secret data. */
+ secret_data_len = ED25519_PUBKEY_LEN + HS_DESC_DESCRIPTOR_COOKIE_LEN;
+ secret_data = tor_malloc(secret_data_len);
+
+ memcpy(secret_data,
+ blinded_pubkey->pubkey,
+ ED25519_PUBKEY_LEN);
+ memcpy(secret_data + ED25519_PUBKEY_LEN,
+ descriptor_cookie,
+ HS_DESC_DESCRIPTOR_COOKIE_LEN);
+ } else {
+ /* If the descriptor cookie is not present, we need only the blinded
+ * pubkey as a secret data. */
+ secret_data_len = ED25519_PUBKEY_LEN;
+ secret_data = tor_malloc(secret_data_len);
+ memcpy(secret_data,
+ blinded_pubkey->pubkey,
+ ED25519_PUBKEY_LEN);
+ }
+
+ *secret_data_out = secret_data;
+ return secret_data_len;
+}
+
/* Generate and encode the superencrypted portion of <b>desc</b>. This also
* involves generating the encrypted portion of the descriptor, and performing
* the superencryption. A newly allocated NUL-terminated string pointer
@@ -902,9 +942,12 @@ encrypt_desc_data_and_base64(const hs_descriptor_t *desc,
* on success else a negative value. */
static int
encode_superencrypted_data(const hs_descriptor_t *desc,
+ const uint8_t *descriptor_cookie,
char **encrypted_blob_out)
{
int ret = -1;
+ uint8_t *secret_data = NULL;
+ size_t secret_data_len = 0;
char *layer2_str = NULL;
char *layer2_b64_ciphertext = NULL;
char *layer1_str = NULL;
@@ -924,8 +967,14 @@ encode_superencrypted_data(const hs_descriptor_t *desc,
goto err;
}
+ secret_data_len = build_secret_data(&desc->plaintext_data.blinded_pubkey,
+ descriptor_cookie,
+ &secret_data);
+
/* Encrypt and b64 the inner layer */
- layer2_b64_ciphertext = encrypt_desc_data_and_base64(desc, layer2_str, 0);
+ layer2_b64_ciphertext =
+ encrypt_desc_data_and_base64(desc, secret_data, secret_data_len,
+ layer2_str, 0);
if (!layer2_b64_ciphertext) {
goto err;
}
@@ -937,7 +986,11 @@ encode_superencrypted_data(const hs_descriptor_t *desc,
}
/* Encrypt and base64 the middle layer */
- layer1_b64_ciphertext = encrypt_desc_data_and_base64(desc, layer1_str, 1);
+ layer1_b64_ciphertext =
+ encrypt_desc_data_and_base64(desc,
+ desc->plaintext_data.blinded_pubkey.pubkey,
+ ED25519_PUBKEY_LEN,
+ layer1_str, 1);
if (!layer1_b64_ciphertext) {
goto err;
}
@@ -946,6 +999,8 @@ encode_superencrypted_data(const hs_descriptor_t *desc,
ret = 0;
err:
+ memwipe(secret_data, 0, secret_data_len);
+ tor_free(secret_data);
tor_free(layer1_str);
tor_free(layer2_str);
tor_free(layer2_b64_ciphertext);
@@ -959,7 +1014,9 @@ encode_superencrypted_data(const hs_descriptor_t *desc,
* and encoded_out is untouched. */
static int
desc_encode_v3(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)
{
int ret = -1;
char *encoded_str = NULL;
@@ -1007,7 +1064,8 @@ desc_encode_v3(const hs_descriptor_t *desc,
/* Build the superencrypted data section. */
{
char *enc_b64_blob=NULL;
- if (encode_superencrypted_data(desc, &enc_b64_blob) < 0) {
+ if (encode_superencrypted_data(desc, descriptor_cookie,
+ &enc_b64_blob) < 0) {
goto err;
}
smartlist_add_asprintf(lines,
@@ -1067,6 +1125,42 @@ desc_encode_v3(const hs_descriptor_t *desc,
/* === DECODING === */
+/* Given the token tok for an auth client, decode it as
+ * hs_desc_authorized_client_t. tok->args MUST contain at least 3 elements
+ * Return 0 on success else -1 on failure. */
+static int
+decode_auth_client(const directory_token_t *tok,
+ hs_desc_authorized_client_t *client)
+{
+ int ret = -1;
+
+ tor_assert(tok);
+ tor_assert(tok->n_args >= 3);
+ tor_assert(client);
+
+ if (base64_decode((char *) client->client_id, sizeof(client->client_id),
+ tok->args[0], strlen(tok->args[0])) !=
+ sizeof(client->client_id)) {
+ goto done;
+ }
+ if (base64_decode((char *) client->iv, sizeof(client->iv),
+ tok->args[1], strlen(tok->args[1])) !=
+ sizeof(client->iv)) {
+ goto done;
+ }
+ if (base64_decode((char *) client->encrypted_cookie,
+ sizeof(client->encrypted_cookie),
+ tok->args[2], strlen(tok->args[2])) !=
+ sizeof(client->encrypted_cookie)) {
+ goto done;
+ }
+
+ /* Success. */
+ ret = 0;
+ done:
+ return ret;
+}
+
/* Given an encoded string of the link specifiers, return a newly allocated
* list of decoded link specifiers. Return NULL on error. */
STATIC smartlist_t *
@@ -1306,11 +1400,81 @@ encrypted_data_length_is_valid(size_t len)
return 0;
}
+/* Decrypt the descriptor cookie given the descriptor, the auth client,
+ * and the client secret key. On sucess, return 0 and a newly allocated
+ * descriptor cookie descriptor_cookie_out. On error or if the client id
+ * is invalid, return -1 and descriptor_cookie_out is set to
+ * NULL. */
+static int
+decrypt_descriptor_cookie(const hs_descriptor_t *desc,
+ const hs_desc_authorized_client_t *client,
+ const curve25519_secret_key_t *client_auth_sk,
+ uint8_t **descriptor_cookie_out)
+{
+ int ret = -1;
+ uint8_t secret_seed[CURVE25519_OUTPUT_LEN];
+ uint8_t keystream[HS_DESC_CLIENT_ID_LEN + HS_DESC_COOKIE_KEY_LEN];
+ uint8_t *cookie_key = NULL;
+ uint8_t *descriptor_cookie = NULL;
+ crypto_cipher_t *cipher = NULL;
+ crypto_xof_t *xof = NULL;
+
+ tor_assert(desc);
+ tor_assert(client);
+ tor_assert(client_auth_sk);
+ tor_assert(!tor_mem_is_zero(
+ (char *) &desc->superencrypted_data.auth_ephemeral_pubkey,
+ sizeof(desc->superencrypted_data.auth_ephemeral_pubkey)));
+ tor_assert(!tor_mem_is_zero((char *) client_auth_sk,
+ sizeof(*client_auth_sk)));
+ tor_assert(!tor_mem_is_zero((char *) desc->subcredential, DIGEST256_LEN));
+
+ /* Calculate x25519(client_x, hs_Y) */
+ curve25519_handshake(secret_seed, client_auth_sk,
+ &desc->superencrypted_data.auth_ephemeral_pubkey);
+
+ /* Calculate KEYS = KDF(subcredential | SECRET_SEED, 40) */
+ xof = crypto_xof_new();
+ crypto_xof_add_bytes(xof, desc->subcredential, DIGEST256_LEN);
+ crypto_xof_add_bytes(xof, secret_seed, sizeof(secret_seed));
+ crypto_xof_squeeze_bytes(xof, keystream, sizeof(keystream));
+ crypto_xof_free(xof);
+
+ /* If the client id of auth client is not the same as the calculcated
+ * client id, it means that this auth client is invaild according to the
+ * client secret key client_auth_sk. */
+ if (tor_memneq(client->client_id, keystream, HS_DESC_CLIENT_ID_LEN)) {
+ goto done;
+ }
+ cookie_key = keystream + HS_DESC_CLIENT_ID_LEN;
+
+ /* This creates a cipher for AES. It can't fail. */
+ cipher = crypto_cipher_new_with_iv_and_bits(cookie_key, client->iv,
+ HS_DESC_COOKIE_KEY_BIT_SIZE);
+ descriptor_cookie = tor_malloc_zero(HS_DESC_DESCRIPTOR_COOKIE_LEN);
+ /* This can't fail. */
+ crypto_cipher_decrypt(cipher, (char *) descriptor_cookie,
+ (const char *) client->encrypted_cookie,
+ sizeof(client->encrypted_cookie));
+
+ /* Success. */
+ ret = 0;
+ done:
+ *descriptor_cookie_out = descriptor_cookie;
+ if (cipher) {
+ crypto_cipher_free(cipher);
+ }
+ memwipe(secret_seed, 0, sizeof(secret_seed));
+ memwipe(keystream, 0, sizeof(keystream));
+ return ret;
+}
+
/** Decrypt an encrypted descriptor layer at <b>encrypted_blob</b> of size
- * <b>encrypted_blob_size</b>. Use the descriptor object <b>desc</b> to
- * generate the right decryption keys; set <b>decrypted_out</b> to the
- * plaintext. If <b>is_superencrypted_layer</b> is set, this is the outter
- * encrypted layer of the descriptor.
+ * <b>encrypted_blob_size</b>. The descriptor cookie is optional. Use
+ * the descriptor object <b>desc</b> and <b>descriptor_cookie</b>
+ * to generate the right decryption keys; set <b>decrypted_out</b> to
+ * the plaintext. If <b>is_superencrypted_layer</b> is set, this is
+ * the outter encrypted layer of the descriptor.
*
* On any error case, including an empty output, return 0 and set
* *<b>decrypted_out</b> to NULL.
@@ -1319,11 +1483,14 @@ MOCK_IMPL(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))
{
uint8_t *decrypted = NULL;
uint8_t secret_key[HS_DESC_ENCRYPTED_KEY_LEN], secret_iv[CIPHER_IV_LEN];
+ uint8_t *secret_data = NULL;
+ size_t secret_data_len = 0;
uint8_t mac_key[DIGEST256_LEN], our_mac[DIGEST256_LEN];
const uint8_t *salt, *encrypted, *desc_mac;
size_t encrypted_len, result_len = 0;
@@ -1350,9 +1517,15 @@ decrypt_desc_layer,(const hs_descriptor_t *desc,
/* And last comes the MAC. */
desc_mac = encrypted_blob + encrypted_blob_size - DIGEST256_LEN;
+ /* Build secret data to be used in the decryption. */
+ secret_data_len = build_secret_data(&desc->plaintext_data.blinded_pubkey,
+ descriptor_cookie,
+ &secret_data);
+
/* KDF construction resulting in a key from which the secret key, IV and MAC
* key are extracted which is what we need for the decryption. */
- build_secret_key_iv_mac(desc, salt, HS_DESC_ENCRYPTED_SALT_LEN,
+ build_secret_key_iv_mac(desc, secret_data, secret_data_len,
+ salt, HS_DESC_ENCRYPTED_SALT_LEN,
secret_key, sizeof(secret_key),
secret_iv, sizeof(secret_iv),
mac_key, sizeof(mac_key),
@@ -1412,167 +1585,98 @@ decrypt_desc_layer,(const hs_descriptor_t *desc,
result_len = 0;
done:
+ memwipe(secret_data, 0, secret_data_len);
memwipe(secret_key, 0, sizeof(secret_key));
memwipe(secret_iv, 0, sizeof(secret_iv));
+ tor_free(secret_data);
return result_len;
}
-/* Basic validation that the superencrypted client auth portion of the
- * descriptor is well-formed and recognized. Return True if so, otherwise
- * return False. */
-static int
-superencrypted_auth_data_is_valid(smartlist_t *tokens)
-{
- /* XXX: This is just basic validation for now. When we implement client auth,
- we can refactor this function so that it actually parses and saves the
- data. */
-
- { /* verify desc auth type */
- const directory_token_t *tok;
- tok = find_by_keyword(tokens, R3_DESC_AUTH_TYPE);
- tor_assert(tok->n_args >= 1);
- if (strcmp(tok->args[0], "x25519")) {
- log_warn(LD_DIR, "Unrecognized desc auth type");
- return 0;
- }
- }
-
- { /* verify desc auth key */
- const directory_token_t *tok;
- curve25519_public_key_t k;
- tok = find_by_keyword(tokens, R3_DESC_AUTH_KEY);
- tor_assert(tok->n_args >= 1);
- if (curve25519_public_from_base64(&k, tok->args[0]) < 0) {
- log_warn(LD_DIR, "Bogus desc auth key in HS desc");
- return 0;
- }
- }
-
- /* verify desc auth client items */
- SMARTLIST_FOREACH_BEGIN(tokens, const directory_token_t *, tok) {
- if (tok->tp == R3_DESC_AUTH_CLIENT) {
- tor_assert(tok->n_args >= 3);
- }
- } SMARTLIST_FOREACH_END(tok);
-
- return 1;
-}
-
-/* Parse <b>message</b>, the plaintext of the superencrypted portion of an HS
- * descriptor. Set <b>encrypted_out</b> to the encrypted blob, and return its
- * size */
-STATIC size_t
-decode_superencrypted(const char *message, size_t message_len,
- uint8_t **encrypted_out)
-{
- int retval = 0;
- memarea_t *area = NULL;
- smartlist_t *tokens = NULL;
-
- area = memarea_new();
- tokens = smartlist_new();
- if (tokenize_string(area, message, message + message_len, tokens,
- hs_desc_superencrypted_v3_token_table, 0) < 0) {
- log_warn(LD_REND, "Superencrypted portion is not parseable");
- goto err;
- }
-
- /* Do some rudimentary validation of the authentication data */
- if (!superencrypted_auth_data_is_valid(tokens)) {
- log_warn(LD_REND, "Invalid auth data");
- goto err;
- }
-
- /* Extract the encrypted data section. */
- {
- const directory_token_t *tok;
- tok = find_by_keyword(tokens, R3_ENCRYPTED);
- tor_assert(tok->object_body);
- if (strcmp(tok->object_type, "MESSAGE") != 0) {
- log_warn(LD_REND, "Desc superencrypted data section is invalid");
- goto err;
- }
- /* Make sure the length of the encrypted blob is valid. */
- if (!encrypted_data_length_is_valid(tok->object_size)) {
- goto err;
- }
-
- /* Copy the encrypted blob to the descriptor object so we can handle it
- * latter if needed. */
- tor_assert(tok->object_size <= INT_MAX);
- *encrypted_out = tor_memdup(tok->object_body, tok->object_size);
- retval = (int) tok->object_size;
- }
-
- err:
- SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
- smartlist_free(tokens);
- if (area) {
- memarea_drop_all(area);
- }
-
- return retval;
-}
-
-/* Decrypt both the superencrypted and the encrypted section of the descriptor
- * using the given descriptor object <b>desc</b>. A newly allocated NUL
- * terminated string is put in decrypted_out which contains the inner encrypted
- * layer of the descriptor. Return the length of decrypted_out on success else
- * 0 is returned and decrypted_out is set to NULL. */
+/* Decrypt the superencrypted section of the descriptor using the given
+ * descriptor object <b>desc</b>. A newly allocated NUL terminated string is
+ * put in decrypted_out which contains the superencrypted layer of the
+ * descriptor. Return the length of decrypted_out on success else 0 is
+ * returned and decrypted_out is set to NULL. */
static size_t
-desc_decrypt_all(const hs_descriptor_t *desc, char **decrypted_out)
+desc_decrypt_superencrypted(const hs_descriptor_t *desc, char **decrypted_out)
{
- size_t decrypted_len = 0;
- size_t encrypted_len = 0;
size_t superencrypted_len = 0;
char *superencrypted_plaintext = NULL;
- uint8_t *encrypted_blob = NULL;
- /** Function logic: This function takes us from the descriptor header to the
- * inner encrypted layer, by decrypting and decoding the middle descriptor
- * layer. In the end we return the contents of the inner encrypted layer to
- * our caller. */
+ tor_assert(desc);
+ tor_assert(decrypted_out);
- /* 1. Decrypt middle layer of descriptor */
superencrypted_len = decrypt_desc_layer(desc,
desc->plaintext_data.superencrypted_blob,
desc->plaintext_data.superencrypted_blob_size,
- 1,
- &superencrypted_plaintext);
+ NULL, 1, &superencrypted_plaintext);
+
if (!superencrypted_len) {
log_warn(LD_REND, "Decrypting superencrypted desc failed.");
- goto err;
+ goto done;
}
tor_assert(superencrypted_plaintext);
- /* 2. Parse "superencrypted" */
- encrypted_len = decode_superencrypted(superencrypted_plaintext,
- superencrypted_len,
- &encrypted_blob);
- if (!encrypted_len) {
- log_warn(LD_REND, "Decrypting encrypted desc failed.");
- goto err;
+ done:
+ /* In case of error, superencrypted_plaintext is already NULL, so the
+ * following line makes sense. */
+ *decrypted_out = superencrypted_plaintext;
+ /* This makes sense too, because, in case of error, this is zero. */
+ return superencrypted_len;
+}
+
+/* Decrypt the encrypted section of the descriptor using the given descriptor
+ * object <b>desc</b>. A newly allocated NUL terminated string is put in
+ * decrypted_out which contains the encrypted layer of the descriptor.
+ * Return the length of decrypted_out on success else 0 is returned and
+ * decrypted_out is set to NULL. */
+static size_t
+desc_decrypt_encrypted(const hs_descriptor_t *desc,
+ const curve25519_secret_key_t *client_auth_sk,
+ char **decrypted_out)
+{
+ size_t encrypted_len = 0;
+ char *encrypted_plaintext = NULL;
+ uint8_t *descriptor_cookie = NULL;
+
+ tor_assert(desc);
+ tor_assert(desc->superencrypted_data.clients);
+ tor_assert(decrypted_out);
+
+ /* If the client secret key is provided, try to find a valid descriptor
+ * cookie. Otherwise, leave it NULL. */
+ if (client_auth_sk) {
+ SMARTLIST_FOREACH_BEGIN(desc->superencrypted_data.clients,
+ hs_desc_authorized_client_t *, client) {
+ /* If we can decrypt the descriptor cookie successfully, we will use that
+ * descriptor cookie and break from the loop. */
+ if (!decrypt_descriptor_cookie(desc, client, client_auth_sk,
+ &descriptor_cookie)) {
+ break;
+ }
+ } SMARTLIST_FOREACH_END(client);
}
- tor_assert(encrypted_blob);
- /* 3. Decrypt "encrypted" and set decrypted_out */
- char *decrypted_desc;
- decrypted_len = decrypt_desc_layer(desc,
- encrypted_blob, encrypted_len,
- 0, &decrypted_desc);
- if (!decrypted_len) {
+ encrypted_len = decrypt_desc_layer(desc,
+ desc->superencrypted_data.encrypted_blob,
+ desc->superencrypted_data.encrypted_blob_size,
+ descriptor_cookie, 0, &encrypted_plaintext);
+ if (!encrypted_len) {
log_warn(LD_REND, "Decrypting encrypted desc failed.");
goto err;
}
- tor_assert(decrypted_desc);
-
- *decrypted_out = decrypted_desc;
+ tor_assert(encrypted_plaintext);
err:
- tor_free(superencrypted_plaintext);
- tor_free(encrypted_blob);
-
- return decrypted_len;
+ /* In case of error, encrypted_plaintext is already NULL, so the
+ * following line makes sense. */
+ *decrypted_out = encrypted_plaintext;
+ if (descriptor_cookie) {
+ memwipe(descriptor_cookie, 0, HS_DESC_DESCRIPTOR_COOKIE_LEN);
+ }
+ tor_free(descriptor_cookie);
+ /* This makes sense too, because, in case of error, this is zero. */
+ return encrypted_len;
}
/* Given the token tok for an intro point legacy key, the list of tokens, the
@@ -1999,19 +2103,19 @@ desc_decode_plaintext_v3(smartlist_t *tokens,
goto err;
}
- /* Extract the encrypted data section. */
+ /* Extract the superencrypted data section. */
tok = find_by_keyword(tokens, R3_SUPERENCRYPTED);
tor_assert(tok->object_body);
if (strcmp(tok->object_type, "MESSAGE") != 0) {
- log_warn(LD_REND, "Service descriptor encrypted data section is invalid");
+ log_warn(LD_REND, "Desc superencrypted data section is invalid");
goto err;
}
- /* Make sure the length of the encrypted blob is valid. */
+ /* Make sure the length of the superencrypted blob is valid. */
if (!encrypted_data_length_is_valid(tok->object_size)) {
goto err;
}
- /* Copy the encrypted blob to the descriptor object so we can handle it
+ /* Copy the superencrypted blob to the descriptor object so we can handle it
* latter if needed. */
desc->superencrypted_blob = tor_memdup(tok->object_body, tok->object_size);
desc->superencrypted_blob_size = tok->object_size;
@@ -2031,14 +2135,130 @@ desc_decode_plaintext_v3(smartlist_t *tokens,
return -1;
}
+/* Decode the version 3 superencrypted section of the given descriptor desc.
+ * The desc_superencrypted_out will be populated with the decoded data.
+ * Return 0 on success else -1. */
+static int
+desc_decode_superencrypted_v3(const hs_descriptor_t *desc,
+ hs_desc_superencrypted_data_t *
+ desc_superencrypted_out)
+{
+ int ret = -1;
+ char *message = NULL;
+ size_t message_len;
+ memarea_t *area = NULL;
+ directory_token_t *tok;
+ smartlist_t *tokens = NULL;
+ /* Rename the parameter because it is too long. */
+ hs_desc_superencrypted_data_t *superencrypted = desc_superencrypted_out;
+
+ tor_assert(desc);
+ tor_assert(desc_superencrypted_out);
+
+ /* Decrypt the superencrypted data that is located in the plaintext section
+ * in the descriptor as a blob of bytes. */
+ message_len = desc_decrypt_superencrypted(desc, &message);
+ if (!message_len) {
+ log_warn(LD_REND, "Service descriptor decryption failed.");
+ goto err;
+ }
+ tor_assert(message);
+
+ area = memarea_new();
+ tokens = smartlist_new();
+ if (tokenize_string(area, message, message + message_len,
+ tokens, hs_desc_superencrypted_v3_token_table, 0) < 0) {
+ log_warn(LD_REND, "Superencrypted service descriptor is not parseable.");
+ goto err;
+ }
+
+ /* Verify desc auth type */
+ tok = find_by_keyword(tokens, R3_DESC_AUTH_TYPE);
+ tor_assert(tok->n_args >= 1);
+ if (strcmp(tok->args[0], "x25519")) {
+ log_warn(LD_DIR, "Unrecognized desc auth type");
+ goto err;
+ }
+
+ /* Extract desc auth ephemeral key */
+ tok = find_by_keyword(tokens, R3_DESC_AUTH_KEY);
+ tor_assert(tok->n_args >= 1);
+ if (curve25519_public_from_base64(&superencrypted->auth_ephemeral_pubkey,
+ tok->args[0]) < 0) {
+ log_warn(LD_DIR, "Bogus desc auth ephemeral key in HS desc");
+ goto err;
+ }
+
+ /* Extract desc auth client items */
+ if (!superencrypted->clients) {
+ superencrypted->clients = smartlist_new();
+ }
+ SMARTLIST_FOREACH_BEGIN(tokens, const directory_token_t *, token) {
+ if (token->tp == R3_DESC_AUTH_CLIENT) {
+ tor_assert(token->n_args >= 3);
+
+ hs_desc_authorized_client_t *client =
+ tor_malloc_zero(sizeof(hs_desc_authorized_client_t));
+
+ if (decode_auth_client(token, client) < 0) {
+ log_warn(LD_REND, "Descriptor client authorization section can't "
+ "be decoded.");
+ tor_free(client);
+ goto err;
+ }
+ smartlist_add(superencrypted->clients, client);
+ }
+ } SMARTLIST_FOREACH_END(token);
+
+ /* Extract the encrypted data section. */
+ tok = find_by_keyword(tokens, R3_ENCRYPTED);
+ tor_assert(tok->object_body);
+ if (strcmp(tok->object_type, "MESSAGE") != 0) {
+ log_warn(LD_REND, "Desc encrypted data section is invalid");
+ goto err;
+ }
+ /* Make sure the length of the encrypted blob is valid. */
+ if (!encrypted_data_length_is_valid(tok->object_size)) {
+ goto err;
+ }
+
+ /* Copy the encrypted blob to the descriptor object so we can handle it
+ * latter if needed. */
+ tor_assert(tok->object_size <= INT_MAX);
+ superencrypted->encrypted_blob = tor_memdup(tok->object_body,
+ tok->object_size);
+ superencrypted->encrypted_blob_size = tok->object_size;
+
+ ret = 0;
+ goto done;
+
+ err:
+ tor_assert(ret < 0);
+ hs_desc_superencrypted_data_free_contents(desc_superencrypted_out);
+
+ done:
+ if (tokens) {
+ SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
+ smartlist_free(tokens);
+ }
+ if (area) {
+ memarea_drop_all(area);
+ }
+ if (message) {
+ tor_free(message);
+ }
+ return ret;
+}
+
/* Decode the version 3 encrypted section of the given descriptor desc. The
* desc_encrypted_out will be populated with the decoded data. Return 0 on
* success else -1. */
static int
desc_decode_encrypted_v3(const hs_descriptor_t *desc,
+ const curve25519_secret_key_t *client_auth_sk,
hs_desc_encrypted_data_t *desc_encrypted_out)
{
- int result = -1;
+ int ret = -1;
char *message = NULL;
size_t message_len;
memarea_t *area = NULL;
@@ -2048,9 +2268,9 @@ desc_decode_encrypted_v3(const hs_descriptor_t *desc,
tor_assert(desc);
tor_assert(desc_encrypted_out);
- /* Decrypt the superencrypted data that is located in the plaintext section
+ /* Decrypt the encrypted data that is located in the superencrypted section
* in the descriptor as a blob of bytes. */
- message_len = desc_decrypt_all(desc, &message);
+ message_len = desc_decrypt_encrypted(desc, client_auth_sk, &message);
if (!message_len) {
log_warn(LD_REND, "Service descriptor decryption failed.");
goto err;
@@ -2109,12 +2329,12 @@ desc_decode_encrypted_v3(const hs_descriptor_t *desc,
/* NOTE: Unknown fields are allowed because this function could be used to
* decode other descriptor version. */
- result = 0;
+ ret = 0;
goto done;
err:
- tor_assert(result < 0);
- desc_encrypted_data_free_contents(desc_encrypted_out);
+ tor_assert(ret < 0);
+ hs_desc_encrypted_data_free_contents(desc_encrypted_out);
done:
if (tokens) {
@@ -2127,7 +2347,7 @@ desc_decode_encrypted_v3(const hs_descriptor_t *desc,
if (message) {
tor_free(message);
}
- return result;
+ return ret;
}
/* Table of encrypted decode function version specific. The function are
@@ -2135,6 +2355,7 @@ desc_decode_encrypted_v3(const hs_descriptor_t *desc,
static int
(*decode_encrypted_handlers[])(
const hs_descriptor_t *desc,
+ const curve25519_secret_key_t *client_auth_sk,
hs_desc_encrypted_data_t *desc_encrypted) =
{
/* v0 */ NULL, /* v1 */ NULL, /* v2 */ NULL,
@@ -2146,6 +2367,7 @@ static int
* negative value on error. */
int
hs_desc_decode_encrypted(const hs_descriptor_t *desc,
+ const curve25519_secret_key_t *client_auth_sk,
hs_desc_encrypted_data_t *desc_encrypted)
{
int ret;
@@ -2156,9 +2378,9 @@ hs_desc_decode_encrypted(const hs_descriptor_t *desc,
version = desc->plaintext_data.version;
tor_assert(desc_encrypted);
/* Calling this function without an encrypted blob to parse is a code flow
- * error. The plaintext parsing should never succeed in the first place
+ * error. The superencrypted parsing should never succeed in the first place
* without an encrypted section. */
- tor_assert(desc->plaintext_data.superencrypted_blob);
+ tor_assert(desc->superencrypted_data.encrypted_blob);
/* Let's make sure we have a supported version as well. By correctly parsing
* the plaintext, this should not fail. */
if (BUG(!hs_desc_is_supported_version(version))) {
@@ -2171,7 +2393,59 @@ hs_desc_decode_encrypted(const hs_descriptor_t *desc,
tor_assert(decode_encrypted_handlers[version]);
/* Run the version specific plaintext decoder. */
- ret = decode_encrypted_handlers[version](desc, desc_encrypted);
+ ret = decode_encrypted_handlers[version](desc, client_auth_sk,
+ desc_encrypted);
+ if (ret < 0) {
+ goto err;
+ }
+
+ err:
+ return ret;
+}
+
+/* Table of superencrypted decode function version specific. The function are
+ * indexed by the version number so v3 callback is at index 3 in the array. */
+static int
+ (*decode_superencrypted_handlers[])(
+ const hs_descriptor_t *desc,
+ hs_desc_superencrypted_data_t *desc_superencrypted) =
+{
+ /* v0 */ NULL, /* v1 */ NULL, /* v2 */ NULL,
+ desc_decode_superencrypted_v3,
+};
+
+/* Decode the superencrypted data section of the given descriptor and store the
+ * data in the given superencrypted data object. Return 0 on success else a
+ * negative value on error. */
+int
+hs_desc_decode_superencrypted(const hs_descriptor_t *desc,
+ hs_desc_superencrypted_data_t *
+ desc_superencrypted)
+{
+ int ret;
+ uint32_t version;
+
+ tor_assert(desc);
+ /* Ease our life a bit. */
+ version = desc->plaintext_data.version;
+ tor_assert(desc_superencrypted);
+ /* Calling this function without an superencrypted blob to parse is
+ * a code flow error. The plaintext parsing should never succeed in
+ * the first place without an superencrypted section. */
+ tor_assert(desc->plaintext_data.superencrypted_blob);
+ /* Let's make sure we have a supported version as well. By correctly parsing
+ * the plaintext, this should not fail. */
+ if (BUG(!hs_desc_is_supported_version(version))) {
+ ret = -1;
+ goto err;
+ }
+ /* Extra precaution. Having no handler for the supported version should
+ * never happened else we forgot to add it but we bumped the version. */
+ tor_assert(ARRAY_LENGTH(decode_superencrypted_handlers) >= version);
+ tor_assert(decode_superencrypted_handlers[version]);
+
+ /* Run the version specific plaintext decoder. */
+ ret = decode_superencrypted_handlers[version](desc, desc_superencrypted);
if (ret < 0) {
goto err;
}
@@ -2267,13 +2541,15 @@ hs_desc_decode_plaintext(const char *encoded,
}
/* Fully decode an encoded descriptor and set a newly allocated descriptor
- * object in desc_out. Subcredentials are used if not NULL else it's ignored.
+ * object in desc_out. Client secret key is used to decrypt the "encrypted"
+ * section if not NULL else it's ignored.
*
* Return 0 on success. A negative value is returned on error and desc_out is
* set to NULL. */
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 ret = -1;
@@ -2283,8 +2559,9 @@ hs_desc_decode_descriptor(const char *encoded,
desc = tor_malloc_zero(sizeof(hs_descriptor_t));
- /* Subcredentials are optional. */
- if (BUG(!subcredential)) {
+ /* Subcredentials are not optional. */
+ if (BUG(!subcredential ||
+ tor_mem_is_zero((char*)subcredential, DIGEST256_LEN))) {
log_warn(LD_GENERAL, "Tried to decrypt without subcred. Impossible!");
goto err;
}
@@ -2296,7 +2573,12 @@ hs_desc_decode_descriptor(const char *encoded,
goto err;
}
- ret = hs_desc_decode_encrypted(desc, &desc->encrypted_data);
+ ret = hs_desc_decode_superencrypted(desc, &desc->superencrypted_data);
+ if (ret < 0) {
+ goto err;
+ }
+
+ ret = hs_desc_decode_encrypted(desc, client_auth_sk, &desc->encrypted_data);
if (ret < 0) {
goto err;
}
@@ -2324,6 +2606,7 @@ static int
(*encode_handlers[])(
const hs_descriptor_t *desc,
const ed25519_keypair_t *signing_kp,
+ const uint8_t *descriptor_cookie,
char **encoded_out) =
{
/* v0 */ NULL, /* v1 */ NULL, /* v2 */ NULL,
@@ -2331,14 +2614,20 @@ static int
};
/* Encode the given descriptor desc including signing with the given key pair
- * signing_kp. On success, encoded_out points to a newly allocated NUL
- * terminated string that contains the encoded descriptor as a string.
+ * signing_kp and encrypting with the given descriptor cookie.
+ *
+ * If the client authorization is enabled, descriptor_cookie must be the same
+ * as the one used to build hs_desc_authorized_client_t in the descriptor.
+ * Otherwise, it must be NULL. On success, encoded_out points to a newly
+ * allocated NUL terminated string that contains the encoded descriptor as
+ * a string.
*
* Return 0 on success and encoded_out is a valid pointer. On error, -1 is
* returned and encoded_out is set to NULL. */
MOCK_IMPL(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 ret = -1;
@@ -2357,15 +2646,21 @@ hs_desc_encode_descriptor,(const hs_descriptor_t *desc,
tor_assert(ARRAY_LENGTH(encode_handlers) >= version);
tor_assert(encode_handlers[version]);
- ret = encode_handlers[version](desc, signing_kp, encoded_out);
+ ret = encode_handlers[version](desc, signing_kp,
+ descriptor_cookie, encoded_out);
if (ret < 0) {
goto err;
}
- /* Try to decode what we just encoded. Symmetry is nice! */
- ret = hs_desc_decode_descriptor(*encoded_out, desc->subcredential, NULL);
- if (BUG(ret < 0)) {
- goto err;
+ /* Try to decode what we just encoded. Symmetry is nice!, but it is
+ * symmetric only if the client auth is disabled. That is, the descriptor
+ * cookie will be NULL. */
+ if (!descriptor_cookie) {
+ ret = hs_desc_decode_descriptor(*encoded_out, desc->subcredential,
+ NULL, NULL);
+ if (BUG(ret < 0)) {
+ goto err;
+ }
}
return 0;
@@ -2375,11 +2670,75 @@ hs_desc_encode_descriptor,(const hs_descriptor_t *desc,
return ret;
}
+/* Free the content of the plaintext section of a descriptor. */
+void
+hs_desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc)
+{
+ if (!desc) {
+ return;
+ }
+
+ if (desc->superencrypted_blob) {
+ tor_free(desc->superencrypted_blob);
+ }
+ tor_cert_free(desc->signing_key_cert);
+
+ memwipe(desc, 0, sizeof(*desc));
+}
+
+/* Free the content of the superencrypted section of a descriptor. */
+void
+hs_desc_superencrypted_data_free_contents(hs_desc_superencrypted_data_t *desc)
+{
+ if (!desc) {
+ return;
+ }
+
+ if (desc->encrypted_blob) {
+ tor_free(desc->encrypted_blob);
+ }
+ if (desc->clients) {
+ SMARTLIST_FOREACH(desc->clients, hs_desc_authorized_client_t *, client,
+ hs_desc_authorized_client_free(client));
+ smartlist_free(desc->clients);
+ }
+
+ memwipe(desc, 0, sizeof(*desc));
+}
+
+/* Free the content of the encrypted section of a descriptor. */
+void
+hs_desc_encrypted_data_free_contents(hs_desc_encrypted_data_t *desc)
+{
+ if (!desc) {
+ return;
+ }
+
+ if (desc->intro_auth_types) {
+ SMARTLIST_FOREACH(desc->intro_auth_types, char *, a, tor_free(a));
+ smartlist_free(desc->intro_auth_types);
+ }
+ if (desc->intro_points) {
+ SMARTLIST_FOREACH(desc->intro_points, hs_desc_intro_point_t *, ip,
+ hs_desc_intro_point_free(ip));
+ smartlist_free(desc->intro_points);
+ }
+ memwipe(desc, 0, sizeof(*desc));
+}
+
/* Free the descriptor plaintext data object. */
void
hs_desc_plaintext_data_free_(hs_desc_plaintext_data_t *desc)
{
- desc_plaintext_data_free_contents(desc);
+ hs_desc_plaintext_data_free_contents(desc);
+ tor_free(desc);
+}
+
+/* Free the descriptor plaintext data object. */
+void
+hs_desc_superencrypted_data_free_(hs_desc_superencrypted_data_t *desc)
+{
+ hs_desc_superencrypted_data_free_contents(desc);
tor_free(desc);
}
@@ -2387,7 +2746,7 @@ hs_desc_plaintext_data_free_(hs_desc_plaintext_data_t *desc)
void
hs_desc_encrypted_data_free_(hs_desc_encrypted_data_t *desc)
{
- desc_encrypted_data_free_contents(desc);
+ hs_desc_encrypted_data_free_contents(desc);
tor_free(desc);
}
@@ -2399,8 +2758,9 @@ hs_descriptor_free_(hs_descriptor_t *desc)
return;
}
- desc_plaintext_data_free_contents(&desc->plaintext_data);
- desc_encrypted_data_free_contents(&desc->encrypted_data);
+ hs_desc_plaintext_data_free_contents(&desc->plaintext_data);
+ hs_desc_superencrypted_data_free_contents(&desc->superencrypted_data);
+ hs_desc_encrypted_data_free_contents(&desc->encrypted_data);
tor_free(desc);
}
@@ -2475,6 +2835,94 @@ hs_desc_intro_point_free_(hs_desc_intro_point_t *ip)
tor_free(ip);
}
+/* Allocate and build a new fake client info for the descriptor. Return a
+ * newly allocated object. This can't fail. */
+hs_desc_authorized_client_t *
+hs_desc_build_fake_authorized_client(void)
+{
+ hs_desc_authorized_client_t *client_auth =
+ tor_malloc_zero(sizeof(*client_auth));
+
+ crypto_rand((char *) client_auth->client_id,
+ sizeof(client_auth->client_id));
+ crypto_rand((char *) client_auth->iv,
+ sizeof(client_auth->iv));
+ crypto_rand((char *) client_auth->encrypted_cookie,
+ sizeof(client_auth->encrypted_cookie));
+
+ return client_auth;
+}
+
+/* Using the service's subcredential, client public key, auth ephemeral secret
+ * key, and descriptor cookie, build the auth client so we can then encode the
+ * descriptor for publication. client_out must be already allocated. */
+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)
+{
+ uint8_t secret_seed[CURVE25519_OUTPUT_LEN];
+ uint8_t keystream[HS_DESC_CLIENT_ID_LEN + HS_DESC_COOKIE_KEY_LEN];
+ uint8_t *cookie_key;
+ crypto_cipher_t *cipher;
+ crypto_xof_t *xof;
+
+ tor_assert(client_auth_pk);
+ tor_assert(auth_ephemeral_sk);
+ tor_assert(descriptor_cookie);
+ tor_assert(client_out);
+ tor_assert(subcredential);
+ tor_assert(!tor_mem_is_zero((char *) auth_ephemeral_sk,
+ sizeof(*auth_ephemeral_sk)));
+ tor_assert(!tor_mem_is_zero((char *) client_auth_pk,
+ sizeof(*client_auth_pk)));
+ tor_assert(!tor_mem_is_zero((char *) descriptor_cookie,
+ HS_DESC_DESCRIPTOR_COOKIE_LEN));
+ tor_assert(!tor_mem_is_zero((char *) subcredential,
+ DIGEST256_LEN));
+
+ /* Calculate x25519(hs_y, client_X) */
+ curve25519_handshake(secret_seed,
+ auth_ephemeral_sk,
+ client_auth_pk);
+
+ /* Calculate KEYS = KDF(subcredential | SECRET_SEED, 40) */
+ xof = crypto_xof_new();
+ crypto_xof_add_bytes(xof, subcredential, DIGEST256_LEN);
+ crypto_xof_add_bytes(xof, secret_seed, sizeof(secret_seed));
+ crypto_xof_squeeze_bytes(xof, keystream, sizeof(keystream));
+ crypto_xof_free(xof);
+
+ memcpy(client_out->client_id, keystream, HS_DESC_CLIENT_ID_LEN);
+ cookie_key = keystream + HS_DESC_CLIENT_ID_LEN;
+
+ /* Random IV */
+ crypto_strongest_rand(client_out->iv, sizeof(client_out->iv));
+
+ /* This creates a cipher for AES. It can't fail. */
+ cipher = crypto_cipher_new_with_iv_and_bits(cookie_key, client_out->iv,
+ HS_DESC_COOKIE_KEY_BIT_SIZE);
+ /* This can't fail. */
+ crypto_cipher_encrypt(cipher, (char *) client_out->encrypted_cookie,
+ (const char *) descriptor_cookie,
+ HS_DESC_DESCRIPTOR_COOKIE_LEN);
+
+ memwipe(secret_seed, 0, sizeof(secret_seed));
+ memwipe(keystream, 0, sizeof(keystream));
+
+ crypto_cipher_free(cipher);
+}
+
+/* Free an authoriezd client object. */
+void
+hs_desc_authorized_client_free_(hs_desc_authorized_client_t *client)
+{
+ tor_free(client);
+}
+
/* Free the given descriptor link specifier. */
void
hs_desc_link_specifier_free_(hs_desc_link_specifier_t *ls)
diff --git a/src/feature/hs/hs_descriptor.h b/src/feature/hs/hs_descriptor.h
index bfdf7559c6..adfb94deaa 100644
--- a/src/feature/hs/hs_descriptor.h
+++ b/src/feature/hs/hs_descriptor.h
@@ -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));
diff --git a/src/feature/hs/hs_service.c b/src/feature/hs/hs_service.c
index 30d01540f2..43e5626a57 100644
--- a/src/feature/hs/hs_service.c
+++ b/src/feature/hs/hs_service.c
@@ -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
diff --git a/src/feature/hs/hs_service.h b/src/feature/hs/hs_service.h
index 17c654ecf4..735266071f 100644
--- a/src/feature/hs/hs_service.h
+++ b/src/feature/hs/hs_service.h
@@ -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) */