diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/app/config/config.c | 7 | ||||
-rw-r--r-- | src/app/config/or_options_st.h | 2 | ||||
-rw-r--r-- | src/feature/hs/hs_client.c | 266 | ||||
-rw-r--r-- | src/feature/hs/hs_client.h | 24 | ||||
-rw-r--r-- | src/feature/hs/hs_config.c | 27 | ||||
-rw-r--r-- | src/feature/hs/hs_config.h | 1 | ||||
-rw-r--r-- | src/feature/hs/hs_descriptor.c | 1046 | ||||
-rw-r--r-- | src/feature/hs/hs_descriptor.h | 84 | ||||
-rw-r--r-- | src/feature/hs/hs_service.c | 525 | ||||
-rw-r--r-- | src/feature/hs/hs_service.h | 36 | ||||
-rw-r--r-- | src/lib/crypt_ops/crypto_rand.c | 4 | ||||
-rw-r--r-- | src/lib/crypt_ops/crypto_rand.h | 2 | ||||
-rw-r--r-- | src/test/fuzz/fuzz_hsdescv3.c | 4 | ||||
-rw-r--r-- | src/test/hs_test_helpers.c | 43 | ||||
-rw-r--r-- | src/test/test_hs_cache.c | 20 | ||||
-rw-r--r-- | src/test/test_hs_client.c | 178 | ||||
-rw-r--r-- | src/test/test_hs_common.c | 6 | ||||
-rw-r--r-- | src/test/test_hs_descriptor.c | 262 | ||||
-rw-r--r-- | src/test/test_hs_service.c | 459 | ||||
-rw-r--r-- | src/test/testing_common.c | 4 |
20 files changed, 2560 insertions, 440 deletions
diff --git a/src/app/config/config.c b/src/app/config/config.c index 6d5b44f1f4..5f46751f93 100644 --- a/src/app/config/config.c +++ b/src/app/config/config.c @@ -459,6 +459,7 @@ static config_var_t option_vars_[] = { VAR("HiddenServiceNumIntroductionPoints", LINELIST_S, RendConfigLines, NULL), VAR("HiddenServiceStatistics", BOOL, HiddenServiceStatistics_option, "1"), V(HidServAuth, LINELIST, NULL), + V(ClientOnionAuthDir, FILENAME, NULL), OBSOLETE("CloseHSClientCircuitsImmediatelyOnTimeout"), OBSOLETE("CloseHSServiceRendCircuitsImmediatelyOnTimeout"), V(HiddenServiceSingleHopMode, BOOL, "0"), @@ -1927,7 +1928,7 @@ options_act(const or_options_t *old_options) // LCOV_EXCL_STOP } - if (running_tor && rend_parse_service_authorization(options, 0) < 0) { + if (running_tor && hs_config_client_auth_all(options, 0) < 0) { // LCOV_EXCL_START log_warn(LD_BUG, "Previously validated client authorization for " "hidden services could not be added!"); @@ -3193,6 +3194,8 @@ warn_about_relative_paths(or_options_t *options) n += warn_if_option_path_is_relative("AccelDir",options->AccelDir); n += warn_if_option_path_is_relative("DataDirectory",options->DataDirectory); n += warn_if_option_path_is_relative("PidFile",options->PidFile); + n += warn_if_option_path_is_relative("ClientOnionAuthDir", + options->ClientOnionAuthDir); for (config_line_t *hs_line = options->RendConfigLines; hs_line; hs_line = hs_line->next) { @@ -4344,7 +4347,7 @@ options_validate(or_options_t *old_options, or_options_t *options, REJECT("Failed to configure rendezvous options. See logs for details."); /* Parse client-side authorization for hidden services. */ - if (rend_parse_service_authorization(options, 1) < 0) + if (hs_config_client_auth_all(options, 1) < 0) REJECT("Failed to configure client authorization for hidden services. " "See logs for details."); diff --git a/src/app/config/or_options_st.h b/src/app/config/or_options_st.h index 9876a41614..3524b99b53 100644 --- a/src/app/config/or_options_st.h +++ b/src/app/config/or_options_st.h @@ -380,6 +380,8 @@ struct or_options_t { struct config_line_t *HidServAuth; /**< List of configuration lines for * client-side authorizations for hidden * services */ + char *ClientOnionAuthDir; /**< Directory to keep client + * onion service authorization secret keys */ char *ContactInfo; /**< Contact info to be published in the directory. */ int HeartbeatPeriod; /**< Log heartbeat messages after this many seconds 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) */ diff --git a/src/lib/crypt_ops/crypto_rand.c b/src/lib/crypt_ops/crypto_rand.c index 78471bf398..313d829a57 100644 --- a/src/lib/crypt_ops/crypto_rand.c +++ b/src/lib/crypt_ops/crypto_rand.c @@ -335,8 +335,8 @@ crypto_strongest_rand_raw(uint8_t *out, size_t out_len) * Try to get <b>out_len</b> bytes of the strongest entropy we can generate, * storing it into <b>out</b>. **/ -void -crypto_strongest_rand(uint8_t *out, size_t out_len) +MOCK_IMPL(void, +crypto_strongest_rand,(uint8_t *out, size_t out_len)) { #define DLEN DIGEST512_LEN diff --git a/src/lib/crypt_ops/crypto_rand.h b/src/lib/crypt_ops/crypto_rand.h index 938f11909e..25bcfa1f1c 100644 --- a/src/lib/crypt_ops/crypto_rand.h +++ b/src/lib/crypt_ops/crypto_rand.h @@ -21,7 +21,7 @@ int crypto_seed_rng(void) ATTR_WUR; MOCK_DECL(void,crypto_rand,(char *to, size_t n)); void crypto_rand_unmocked(char *to, size_t n); -void crypto_strongest_rand(uint8_t *out, size_t out_len); +MOCK_DECL(void,crypto_strongest_rand,(uint8_t *out, size_t out_len)); int crypto_rand_int(unsigned int max); int crypto_rand_int_range(unsigned int min, unsigned int max); uint64_t crypto_rand_uint64_range(uint64_t min, uint64_t max); diff --git a/src/test/fuzz/fuzz_hsdescv3.c b/src/test/fuzz/fuzz_hsdescv3.c index 4ec8db0a87..b332973b39 100644 --- a/src/test/fuzz/fuzz_hsdescv3.c +++ b/src/test/fuzz/fuzz_hsdescv3.c @@ -38,11 +38,13 @@ static size_t mock_decrypt_desc_layer(const hs_descriptor_t *desc, const uint8_t *encrypted_blob, size_t encrypted_blob_size, + const uint8_t *descriptor_cookie, int is_superencrypted_layer, char **decrypted_out) { (void)is_superencrypted_layer; (void)desc; + (void)descriptor_cookie; const size_t overhead = HS_DESC_ENCRYPTED_SALT_LEN + DIGEST256_LEN; if (encrypted_blob_size < overhead) return 0; @@ -84,7 +86,7 @@ fuzz_main(const uint8_t *data, size_t sz) char *fuzzing_data = tor_memdup_nulterm(data, sz); memset(subcredential, 'A', sizeof(subcredential)); - hs_desc_decode_descriptor(fuzzing_data, subcredential, &desc); + hs_desc_decode_descriptor(fuzzing_data, subcredential, NULL, &desc); if (desc) { log_debug(LD_GENERAL, "Decoding okay"); hs_descriptor_free(desc); diff --git a/src/test/hs_test_helpers.c b/src/test/hs_test_helpers.c index afe3eafa2f..4e13ba43a7 100644 --- a/src/test/hs_test_helpers.c +++ b/src/test/hs_test_helpers.c @@ -98,8 +98,11 @@ static hs_descriptor_t * hs_helper_build_hs_desc_impl(unsigned int no_ip, const ed25519_keypair_t *signing_kp) { + int ret; + int i; time_t now = approx_time(); ed25519_keypair_t blinded_kp; + curve25519_keypair_t auth_ephemeral_kp; hs_descriptor_t *descp = NULL, *desc = tor_malloc_zero(sizeof(*desc)); desc->plaintext_data.version = HS_DESC_SUPPORTED_FORMAT_VERSION_MAX; @@ -126,6 +129,20 @@ hs_helper_build_hs_desc_impl(unsigned int no_ip, hs_get_subcredential(&signing_kp->pubkey, &blinded_kp.pubkey, desc->subcredential); + /* Setup superencrypted data section. */ + ret = curve25519_keypair_generate(&auth_ephemeral_kp, 0); + tt_int_op(ret, ==, 0); + memcpy(&desc->superencrypted_data.auth_ephemeral_pubkey, + &auth_ephemeral_kp.pubkey, + sizeof(curve25519_public_key_t)); + + desc->superencrypted_data.clients = smartlist_new(); + for (i = 0; i < HS_DESC_AUTH_CLIENT_MULTIPLE; i++) { + hs_desc_authorized_client_t *desc_client = + hs_desc_build_fake_authorized_client(); + smartlist_add(desc->superencrypted_data.clients, desc_client); + } + /* Setup encrypted data section. */ desc->encrypted_data.create2_ntor = 1; desc->encrypted_data.intro_auth_types = smartlist_new(); @@ -207,6 +224,32 @@ hs_helper_desc_equal(const hs_descriptor_t *desc1, * encrypted blob. As contrast to the decoding process where we populate a * descriptor object. */ + /* Superencrypted data section. */ + tt_mem_op(desc1->superencrypted_data.auth_ephemeral_pubkey.public_key, OP_EQ, + desc2->superencrypted_data.auth_ephemeral_pubkey.public_key, + CURVE25519_PUBKEY_LEN); + + /* Auth clients. */ + { + tt_assert(desc1->superencrypted_data.clients); + tt_assert(desc2->superencrypted_data.clients); + tt_int_op(smartlist_len(desc1->superencrypted_data.clients), ==, + smartlist_len(desc2->superencrypted_data.clients)); + for (int i=0; + i < smartlist_len(desc1->superencrypted_data.clients); + i++) { + hs_desc_authorized_client_t + *client1 = smartlist_get(desc1->superencrypted_data.clients, i), + *client2 = smartlist_get(desc2->superencrypted_data.clients, i); + tor_memeq(client1->client_id, client2->client_id, + sizeof(client1->client_id)); + tor_memeq(client1->iv, client2->iv, + sizeof(client1->iv)); + tor_memeq(client1->encrypted_cookie, client2->encrypted_cookie, + sizeof(client1->encrypted_cookie)); + } + } + /* Encrypted data section. */ tt_uint_op(desc1->encrypted_data.create2_ntor, ==, desc2->encrypted_data.create2_ntor); diff --git a/src/test/test_hs_cache.c b/src/test/test_hs_cache.c index c1a69af829..728bb4a2f5 100644 --- a/src/test/test_hs_cache.c +++ b/src/test/test_hs_cache.c @@ -64,7 +64,7 @@ test_directory(void *arg) tt_int_op(ret, OP_EQ, 0); desc1 = hs_helper_build_hs_desc_with_ip(&signing_kp1); tt_assert(desc1); - ret = hs_desc_encode_descriptor(desc1, &signing_kp1, &desc1_str); + ret = hs_desc_encode_descriptor(desc1, &signing_kp1, NULL, &desc1_str); tt_int_op(ret, OP_EQ, 0); /* Very first basic test, should be able to be stored, survive a @@ -102,7 +102,7 @@ test_directory(void *arg) desc_zero_lifetime->plaintext_data.lifetime_sec = 0; char *desc_zero_lifetime_str; ret = hs_desc_encode_descriptor(desc_zero_lifetime, &signing_kp_zero, - &desc_zero_lifetime_str); + NULL, &desc_zero_lifetime_str); tt_int_op(ret, OP_EQ, 0); ret = hs_cache_store_as_dir(desc1_str); @@ -153,7 +153,7 @@ test_directory(void *arg) tt_int_op(ret, OP_EQ, 1); /* Bump revision counter. */ desc1->plaintext_data.revision_counter++; - ret = hs_desc_encode_descriptor(desc1, &signing_kp1, &new_desc_str); + ret = hs_desc_encode_descriptor(desc1, &signing_kp1, NULL, &new_desc_str); tt_int_op(ret, OP_EQ, 0); ret = hs_cache_store_as_dir(new_desc_str); tt_int_op(ret, OP_EQ, 0); @@ -187,7 +187,7 @@ test_clean_as_dir(void *arg) tt_int_op(ret, OP_EQ, 0); desc1 = hs_helper_build_hs_desc_with_ip(&signing_kp1); tt_assert(desc1); - ret = hs_desc_encode_descriptor(desc1, &signing_kp1, &desc1_str); + ret = hs_desc_encode_descriptor(desc1, &signing_kp1, NULL, &desc1_str); tt_int_op(ret, OP_EQ, 0); ret = hs_cache_store_as_dir(desc1_str); tt_int_op(ret, OP_EQ, 0); @@ -301,7 +301,7 @@ test_upload_and_download_hs_desc(void *arg) published_desc = hs_helper_build_hs_desc_with_ip(&signing_kp); tt_assert(published_desc); retval = hs_desc_encode_descriptor(published_desc, &signing_kp, - &published_desc_str); + NULL, &published_desc_str); tt_int_op(retval, OP_EQ, 0); } @@ -365,7 +365,7 @@ test_hsdir_revision_counter_check(void *arg) published_desc = hs_helper_build_hs_desc_with_ip(&signing_kp); tt_assert(published_desc); retval = hs_desc_encode_descriptor(published_desc, &signing_kp, - &published_desc_str); + NULL, &published_desc_str); tt_int_op(retval, OP_EQ, 0); } @@ -390,7 +390,7 @@ test_hsdir_revision_counter_check(void *arg) received_desc_str = helper_fetch_desc_from_hsdir(blinded_key); retval = hs_desc_decode_descriptor(received_desc_str, - subcredential, &received_desc); + subcredential, NULL, &received_desc); tt_int_op(retval, OP_EQ, 0); tt_assert(received_desc); @@ -407,7 +407,7 @@ test_hsdir_revision_counter_check(void *arg) published_desc->plaintext_data.revision_counter = 1313; tor_free(published_desc_str); retval = hs_desc_encode_descriptor(published_desc, &signing_kp, - &published_desc_str); + NULL, &published_desc_str); tt_int_op(retval, OP_EQ, 0); retval = handle_post_hs_descriptor("/tor/hs/3/publish",published_desc_str); @@ -423,7 +423,7 @@ test_hsdir_revision_counter_check(void *arg) received_desc_str = helper_fetch_desc_from_hsdir(blinded_key); retval = hs_desc_decode_descriptor(received_desc_str, - subcredential, &received_desc); + subcredential, NULL, &received_desc); tt_int_op(retval, OP_EQ, 0); tt_assert(received_desc); @@ -482,7 +482,7 @@ test_client_cache(void *arg) published_desc = hs_helper_build_hs_desc_with_ip(&signing_kp); tt_assert(published_desc); retval = hs_desc_encode_descriptor(published_desc, &signing_kp, - &published_desc_str); + NULL, &published_desc_str); tt_int_op(retval, OP_EQ, 0); memcpy(wanted_subcredential, published_desc->subcredential, DIGEST256_LEN); tt_assert(!tor_mem_is_zero((char*)wanted_subcredential, DIGEST256_LEN)); diff --git a/src/test/test_hs_client.c b/src/test/test_hs_client.c index 7fcc1db195..c91e82ed4a 100644 --- a/src/test/test_hs_client.c +++ b/src/test/test_hs_client.c @@ -6,6 +6,7 @@ * \brief Test prop224 HS client functionality. */ +#define CONFIG_PRIVATE #define CRYPTO_PRIVATE #define MAIN_PRIVATE #define HS_CLIENT_PRIVATE @@ -32,6 +33,7 @@ #include "feature/hs/hs_circuit.h" #include "feature/hs/hs_circuitmap.h" #include "feature/hs/hs_client.h" +#include "feature/hs/hs_config.h" #include "feature/hs/hs_ident.h" #include "feature/hs/hs_cache.h" #include "core/or/circuitlist.h" @@ -73,6 +75,20 @@ mock_networkstatus_get_live_consensus(time_t now) return &mock_ns; } +static int +helper_config_client(const char *conf, int validate_only) +{ + int ret = 0; + or_options_t *options = NULL; + tt_assert(conf); + options = helper_parse_options(conf); + tt_assert(options); + ret = hs_config_client_auth_all(options, validate_only); + done: + or_options_free(options); + return ret; +} + /* Test helper function: Setup a circuit and a stream with the same hidden * service destination, and put them in <b>circ_out</b> and * <b>conn_out</b>. Make the stream wait for circuits to be established to the @@ -366,7 +382,7 @@ test_client_pick_intro(void *arg) { char *encoded = NULL; desc = hs_helper_build_hs_desc_with_ip(&service_kp); - ret = hs_desc_encode_descriptor(desc, &service_kp, &encoded); + ret = hs_desc_encode_descriptor(desc, &service_kp, NULL, &encoded); tt_int_op(ret, OP_EQ, 0); tt_assert(encoded); @@ -601,6 +617,160 @@ test_descriptor_fetch(void *arg) hs_free_all(); } +static void +test_auth_key_filename_is_valid(void *arg) +{ + (void) arg; + + /* Valid file name. */ + tt_assert(auth_key_filename_is_valid("a.auth_private")); + /* Valid file name with special character. */ + tt_assert(auth_key_filename_is_valid("a-.auth_private")); + /* Invalid extension. */ + tt_assert(!auth_key_filename_is_valid("a.ath_private")); + /* Nothing before the extension. */ + tt_assert(!auth_key_filename_is_valid(".auth_private")); + + done: + ; +} + +static void +test_parse_auth_file_content(void *arg) +{ + hs_client_service_authorization_t *auth = NULL; + + (void) arg; + + /* Valid authorized client. */ + auth = parse_auth_file_content( + "4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad:descriptor:" + "x25519:zdsyvn2jq534ugyiuzgjy4267jbtzcjbsgedhshzx5mforyxtryq"); + tt_assert(auth); + + /* Wrong number of fields. */ + tt_assert(!parse_auth_file_content("a:b")); + /* Wrong auth type. */ + tt_assert(!parse_auth_file_content( + "4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad:x:" + "x25519:zdsyvn2jq534ugyiuzgjy4267jbtzcjbsgedhshzx5mforyxtryq")); + /* Wrong key type. */ + tt_assert(!parse_auth_file_content( + "4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad:descriptor:" + "x:zdsyvn2jq534ugyiuzgjy4267jbtzcjbsgedhshzx5mforyxtryq")); + /* Some malformed string. */ + tt_assert(!parse_auth_file_content("xx:descriptor:x25519:aa==")); + /* Bigger key than it should be */ + tt_assert(!parse_auth_file_content("xx:descriptor:x25519:" + "vjqea4jbhwwc4hto7ekyvqfbeodghbaq6nxi45hz4wr3qvhqv3yqa")); + done: + tor_free(auth); +} + +static char * +mock_read_file_to_str(const char *filename, int flags, struct stat *stat_out) +{ + char *ret = NULL; + + (void) flags; + (void) stat_out; + + if (!strcmp(filename, get_fname("auth_keys" PATH_SEPARATOR + "client1.auth_private"))) { + ret = tor_strdup( + "4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad:descriptor:" + "x25519:zdsyvn2jq534ugyiuzgjy4267jbtzcjbsgedhshzx5mforyxtryq"); + goto done; + } + + if (!strcmp(filename, get_fname("auth_keys" PATH_SEPARATOR "dummy.xxx"))) { + ret = tor_strdup( + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:descriptor:" + "x25519:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + goto done; + } + + if (!strcmp(filename, get_fname("auth_keys" PATH_SEPARATOR + "client2.auth_private"))) { + ret = tor_strdup( + "25njqamcweflpvkl73j4szahhihoc4xt3ktcgjnpaingr5yhkenl5sid:descriptor:" + "x25519:fdreqzjqso7d2ac7qscrxfl5qfpamdvgy5d6cxejcgzc3hvhurmq"); + goto done; + } + + done: + return ret; +} + +static int +mock_check_private_dir(const char *dirname, cpd_check_t check, + const char *effective_user) +{ + (void) dirname; + (void) check; + (void) effective_user; + + return 0; +} + +static smartlist_t * +mock_tor_listdir(const char *dirname) +{ + smartlist_t *file_list = smartlist_new(); + + (void) dirname; + + smartlist_add(file_list, tor_strdup("client1.auth_private")); + smartlist_add(file_list, tor_strdup("dummy.xxx")); + smartlist_add(file_list, tor_strdup("client2.auth_private")); + + return file_list; +} + +static void +test_config_client_authorization(void *arg) +{ + int ret; + char *conf = NULL; + ed25519_public_key_t pk1, pk2; + digest256map_t *global_map = NULL; + char *key_dir = tor_strdup(get_fname("auth_keys")); + + (void) arg; + + MOCK(read_file_to_str, mock_read_file_to_str); + MOCK(tor_listdir, mock_tor_listdir); + MOCK(check_private_dir, mock_check_private_dir); + +#define conf_fmt \ + "ClientOnionAuthDir %s\n" + + tor_asprintf(&conf, conf_fmt, key_dir); + ret = helper_config_client(conf, 0); + tor_free(conf); + tt_int_op(ret, OP_EQ, 0); + +#undef conf_fmt + + global_map = get_hs_client_auths_map(); + tt_int_op(digest256map_size(global_map), OP_EQ, 2); + + hs_parse_address("4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad", + &pk1, NULL, NULL); + hs_parse_address("25njqamcweflpvkl73j4szahhihoc4xt3ktcgjnpaingr5yhkenl5sid", + &pk2, NULL, NULL); + + tt_assert(digest256map_get(global_map, pk1.pubkey)); + tt_assert(digest256map_get(global_map, pk2.pubkey)); + + done: + tor_free(key_dir); + hs_free_all(); + UNMOCK(read_file_to_str); + UNMOCK(tor_listdir); + UNMOCK(check_private_dir); +} + struct testcase_t hs_client_tests[] = { { "e2e_rend_circuit_setup_legacy", test_e2e_rend_circuit_setup_legacy, TT_FORK, NULL, NULL }, @@ -610,5 +780,11 @@ struct testcase_t hs_client_tests[] = { TT_FORK, NULL, NULL }, { "descriptor_fetch", test_descriptor_fetch, TT_FORK, NULL, NULL }, + { "auth_key_filename_is_valid", test_auth_key_filename_is_valid, TT_FORK, + NULL, NULL }, + { "parse_auth_file_content", test_parse_auth_file_content, TT_FORK, + NULL, NULL }, + { "config_client_authorization", test_config_client_authorization, + TT_FORK, NULL, NULL }, END_OF_TESTCASES }; diff --git a/src/test/test_hs_common.c b/src/test/test_hs_common.c index c1001ee5c4..c60d6e2640 100644 --- a/src/test/test_hs_common.c +++ b/src/test/test_hs_common.c @@ -428,11 +428,13 @@ mock_directory_initiate_request(directory_request_t *req) static int mock_hs_desc_encode_descriptor(const hs_descriptor_t *desc, - const ed25519_keypair_t *signing_kp, - char **encoded_out) + const ed25519_keypair_t *signing_kp, + const uint8_t *descriptor_cookie, + char **encoded_out) { (void)desc; (void)signing_kp; + (void)descriptor_cookie; tor_asprintf(encoded_out, "lulu"); return 0; diff --git a/src/test/test_hs_descriptor.c b/src/test/test_hs_descriptor.c index 9a7e66eaea..4889281cb1 100644 --- a/src/test/test_hs_descriptor.c +++ b/src/test/test_hs_descriptor.c @@ -30,6 +30,13 @@ DISABLE_GCC_WARNING(overlength-strings) #include "test_hs_descriptor.inc" ENABLE_GCC_WARNING(overlength-strings) +/* Mock function to fill all bytes with 1 */ +static void +mock_crypto_strongest_rand(uint8_t *out, size_t out_len) +{ + memset(out, 1, out_len); +} + /* Test certificate encoding put in a descriptor. */ static void test_cert_encoding(void *arg) @@ -284,7 +291,6 @@ static void test_encode_descriptor(void *arg) { int ret; - char *encoded = NULL; ed25519_keypair_t signing_kp; hs_descriptor_t *desc = NULL; @@ -293,19 +299,38 @@ test_encode_descriptor(void *arg) ret = ed25519_keypair_generate(&signing_kp, 0); tt_int_op(ret, OP_EQ, 0); desc = hs_helper_build_hs_desc_with_ip(&signing_kp); - ret = hs_desc_encode_descriptor(desc, &signing_kp, &encoded); - tt_int_op(ret, OP_EQ, 0); - tt_assert(encoded); + { + char *encoded = NULL; + ret = hs_desc_encode_descriptor(desc, &signing_kp, NULL, &encoded); + tt_int_op(ret, OP_EQ, 0); + tt_assert(encoded); + + tor_free(encoded); + } + + { + char *encoded = NULL; + uint8_t descriptor_cookie[HS_DESC_DESCRIPTOR_COOKIE_LEN]; + + crypto_strongest_rand(descriptor_cookie, sizeof(descriptor_cookie)); + + ret = hs_desc_encode_descriptor(desc, &signing_kp, + descriptor_cookie, &encoded); + tt_int_op(ret, OP_EQ, 0); + tt_assert(encoded); + + tor_free(encoded); + } done: hs_descriptor_free(desc); - tor_free(encoded); } static void test_decode_descriptor(void *arg) { int ret; + int i; char *encoded = NULL; ed25519_keypair_t signing_kp; hs_descriptor_t *desc = NULL; @@ -323,14 +348,15 @@ test_decode_descriptor(void *arg) subcredential); /* Give some bad stuff to the decoding function. */ - ret = hs_desc_decode_descriptor("hladfjlkjadf", subcredential, &decoded); + ret = hs_desc_decode_descriptor("hladfjlkjadf", subcredential, + NULL, &decoded); tt_int_op(ret, OP_EQ, -1); - ret = hs_desc_encode_descriptor(desc, &signing_kp, &encoded); + ret = hs_desc_encode_descriptor(desc, &signing_kp, NULL, &encoded); tt_int_op(ret, OP_EQ, 0); tt_assert(encoded); - ret = hs_desc_decode_descriptor(encoded, subcredential, &decoded); + ret = hs_desc_decode_descriptor(encoded, subcredential, NULL, &decoded); tt_int_op(ret, OP_EQ, 0); tt_assert(decoded); @@ -346,13 +372,84 @@ test_decode_descriptor(void *arg) desc_no_ip = hs_helper_build_hs_desc_no_ip(&signing_kp_no_ip); tt_assert(desc_no_ip); tor_free(encoded); - ret = hs_desc_encode_descriptor(desc_no_ip, &signing_kp_no_ip, &encoded); + ret = hs_desc_encode_descriptor(desc_no_ip, &signing_kp_no_ip, + NULL, &encoded); + tt_int_op(ret, OP_EQ, 0); + tt_assert(encoded); + hs_descriptor_free(decoded); + ret = hs_desc_decode_descriptor(encoded, subcredential, NULL, &decoded); + tt_int_op(ret, OP_EQ, 0); + tt_assert(decoded); + } + + /* Decode a descriptor with auth clients. */ + { + uint8_t descriptor_cookie[HS_DESC_DESCRIPTOR_COOKIE_LEN]; + curve25519_keypair_t auth_ephemeral_kp; + curve25519_keypair_t client_kp, invalid_client_kp; + smartlist_t *clients; + hs_desc_authorized_client_t *client, *fake_client; + client = tor_malloc_zero(sizeof(hs_desc_authorized_client_t)); + + /* Prepare all the keys needed to build the auth client. */ + curve25519_keypair_generate(&auth_ephemeral_kp, 0); + curve25519_keypair_generate(&client_kp, 0); + curve25519_keypair_generate(&invalid_client_kp, 0); + crypto_strongest_rand(descriptor_cookie, HS_DESC_DESCRIPTOR_COOKIE_LEN); + + memcpy(&desc->superencrypted_data.auth_ephemeral_pubkey, + &auth_ephemeral_kp.pubkey, CURVE25519_PUBKEY_LEN); + + hs_helper_get_subcred_from_identity_keypair(&signing_kp, + subcredential); + + /* Build and add the auth client to the descriptor. */ + clients = desc->superencrypted_data.clients; + if (!clients) { + clients = smartlist_new(); + } + hs_desc_build_authorized_client(subcredential, + &client_kp.pubkey, + &auth_ephemeral_kp.seckey, + descriptor_cookie, client); + smartlist_add(clients, client); + + /* We need to add fake auth clients here. */ + for (i=0; i < 15; ++i) { + fake_client = hs_desc_build_fake_authorized_client(); + smartlist_add(clients, fake_client); + } + desc->superencrypted_data.clients = clients; + + /* Test the encoding/decoding in the following lines. */ + tor_free(encoded); + ret = hs_desc_encode_descriptor(desc, &signing_kp, + descriptor_cookie, &encoded); tt_int_op(ret, OP_EQ, 0); tt_assert(encoded); + + /* If we do not have the client secret key, the decoding must fail. */ hs_descriptor_free(decoded); - ret = hs_desc_decode_descriptor(encoded, subcredential, &decoded); + ret = hs_desc_decode_descriptor(encoded, subcredential, + NULL, &decoded); + tt_int_op(ret, OP_LT, 0); + tt_assert(!decoded); + + /* If we have an invalid client secret key, the decoding must fail. */ + hs_descriptor_free(decoded); + ret = hs_desc_decode_descriptor(encoded, subcredential, + &invalid_client_kp.seckey, &decoded); + tt_int_op(ret, OP_LT, 0); + tt_assert(!decoded); + + /* If we have the client secret key, the decoding must succeed and the + * decoded descriptor must be correct. */ + ret = hs_desc_decode_descriptor(encoded, subcredential, + &client_kp.seckey, &decoded); tt_int_op(ret, OP_EQ, 0); tt_assert(decoded); + + hs_helper_desc_equal(desc, decoded); } done: @@ -588,7 +685,7 @@ test_decode_bad_signature(void *arg) teardown_capture_of_logs(); done: - desc_plaintext_data_free_contents(&desc_plaintext); + hs_desc_plaintext_data_free_contents(&desc_plaintext); } static void @@ -764,101 +861,69 @@ test_desc_signature(void *arg) tor_free(data); } -/* bad desc auth type */ -static const char bad_superencrypted_text1[] = "desc-auth-type scoobysnack\n" - "desc-auth-ephemeral-key A/O8DVtnUheb3r1JqoB8uJB7wxXL1XJX3eny4yB+eFA=\n" - "auth-client oiNrQB8WwKo S5D02W7vKgiWIMygrBl8RQ FB//SfOBmLEx1kViEWWL1g\n" - "encrypted\n" - "-----BEGIN MESSAGE-----\n" - "YmVpbmcgb24gbW91bnRhaW5zLCB0aGlua2luZyBhYm91dCBjb21wdXRlcnMsIGlzIG5vdC" - "BiYWQgYXQgYWxs\n" - "-----END MESSAGE-----\n"; - -/* bad ephemeral key */ -static const char bad_superencrypted_text2[] = "desc-auth-type x25519\n" - "desc-auth-ephemeral-key differentalphabet\n" - "auth-client oiNrQB8WwKo S5D02W7vKgiWIMygrBl8RQ FB//SfOBmLEx1kViEWWL1g\n" - "encrypted\n" - "-----BEGIN MESSAGE-----\n" - "YmVpbmcgb24gbW91bnRhaW5zLCB0aGlua2luZyBhYm91dCBjb21wdXRlcnMsIGlzIG5vdC" - "BiYWQgYXQgYWxs\n" - "-----END MESSAGE-----\n"; - -/* bad encrypted msg */ -static const char bad_superencrypted_text3[] = "desc-auth-type x25519\n" - "desc-auth-ephemeral-key A/O8DVtnUheb3r1JqoB8uJB7wxXL1XJX3eny4yB+eFA=\n" - "auth-client oiNrQB8WwKo S5D02W7vKgiWIMygrBl8RQ FB//SfOBmLEx1kViEWWL1g\n" - "encrypted\n" - "-----BEGIN MESSAGE-----\n" - "SO SMALL NOT GOOD\n" - "-----END MESSAGE-----\n"; - -static const char correct_superencrypted_text[] = "desc-auth-type x25519\n" - "desc-auth-ephemeral-key A/O8DVtnUheb3r1JqoB8uJB7wxXL1XJX3eny4yB+eFA=\n" - "auth-client oiNrQB8WwKo S5D02W7vKgiWIMygrBl8RQ FB//SfOBmLEx1kViEWWL1g\n" - "auth-client Od09Qu636Qo /PKLzqewAdS/+0+vZC+MvQ dpw4NFo13zDnuPz45rxrOg\n" - "auth-client JRr840iGYN0 8s8cxYqF7Lx23+NducC4Qg zAafl4wPLURkuEjJreZq1g\n" - "encrypted\n" - "-----BEGIN MESSAGE-----\n" - "YmVpbmcgb24gbW91bnRhaW5zLCB0aGlua2luZyBhYm91dCBjb21wdXRlcnMsIGlzIG5vdC" - "BiYWQgYXQgYWxs\n" - "-----END MESSAGE-----\n"; - -static const char correct_encrypted_plaintext[] = "being on mountains, " - "thinking about computers, is not bad at all"; - static void -test_parse_hs_desc_superencrypted(void *arg) +test_build_authorized_client(void *arg) { + int ret; + hs_desc_authorized_client_t *desc_client = NULL; + uint8_t descriptor_cookie[HS_DESC_DESCRIPTOR_COOKIE_LEN]; + curve25519_secret_key_t auth_ephemeral_sk; + curve25519_secret_key_t client_auth_sk; + curve25519_public_key_t client_auth_pk; + const char ephemeral_sk_b16[] = + "d023b674d993a5c8446bd2ca97e9961149b3c0e88c7dc14e8777744dd3468d6a"; + const char descriptor_cookie_b16[] = + "07d087f1d8c68393721f6e70316d3b29"; + const char client_pubkey_b16[] = + "8c1298fa6050e372f8598f6deca32e27b0ad457741422c2629ebb132cf7fae37"; + uint8_t subcredential[DIGEST256_LEN]; + char *mem_op_hex_tmp=NULL; + (void) arg; - size_t retval; - uint8_t *encrypted_out = NULL; - { - setup_full_capture_of_logs(LOG_WARN); - retval = decode_superencrypted(bad_superencrypted_text1, - strlen(bad_superencrypted_text1), - &encrypted_out); - tt_u64_op(retval, OP_EQ, 0); - tt_ptr_op(encrypted_out, OP_EQ, NULL); - expect_log_msg_containing("Unrecognized desc auth type"); - teardown_capture_of_logs(); - } + ret = curve25519_secret_key_generate(&auth_ephemeral_sk, 0); + tt_int_op(ret, OP_EQ, 0); - { - setup_full_capture_of_logs(LOG_WARN); - retval = decode_superencrypted(bad_superencrypted_text2, - strlen(bad_superencrypted_text2), - &encrypted_out); - tt_u64_op(retval, OP_EQ, 0); - tt_ptr_op(encrypted_out, OP_EQ, NULL); - expect_log_msg_containing("Bogus desc auth key in HS desc"); - teardown_capture_of_logs(); - } + ret = curve25519_secret_key_generate(&client_auth_sk, 0); + tt_int_op(ret, OP_EQ, 0); + curve25519_public_key_generate(&client_auth_pk, &client_auth_sk); - { - setup_full_capture_of_logs(LOG_WARN); - retval = decode_superencrypted(bad_superencrypted_text3, - strlen(bad_superencrypted_text3), - &encrypted_out); - tt_u64_op(retval, OP_EQ, 0); - tt_ptr_op(encrypted_out, OP_EQ, NULL); - expect_log_msg_containing("Length of descriptor\'s encrypted data " - "is too small."); - teardown_capture_of_logs(); - } + memset(subcredential, 42, sizeof(subcredential)); - /* Now finally the good one */ - retval = decode_superencrypted(correct_superencrypted_text, - strlen(correct_superencrypted_text), - &encrypted_out); + desc_client = tor_malloc_zero(sizeof(hs_desc_authorized_client_t)); - tt_u64_op(retval, OP_EQ, strlen(correct_encrypted_plaintext)); - tt_mem_op(encrypted_out, OP_EQ, correct_encrypted_plaintext, - strlen(correct_encrypted_plaintext)); + base16_decode((char *) &auth_ephemeral_sk, + sizeof(auth_ephemeral_sk), + ephemeral_sk_b16, + strlen(ephemeral_sk_b16)); + + base16_decode((char *) descriptor_cookie, + sizeof(descriptor_cookie), + descriptor_cookie_b16, + strlen(descriptor_cookie_b16)); + + base16_decode((char *) &client_auth_pk, + sizeof(client_auth_pk), + client_pubkey_b16, + strlen(client_pubkey_b16)); + + MOCK(crypto_strongest_rand, mock_crypto_strongest_rand); + + hs_desc_build_authorized_client(subcredential, + &client_auth_pk, &auth_ephemeral_sk, + descriptor_cookie, desc_client); + + test_memeq_hex((char *) desc_client->client_id, + "EC19B7FF4D2DDA13"); + test_memeq_hex((char *) desc_client->iv, + "01010101010101010101010101010101"); + test_memeq_hex((char *) desc_client->encrypted_cookie, + "B21222BE13F385F355BD07B2381F9F29"); done: - tor_free(encrypted_out); + tor_free(desc_client); + tor_free(mem_op_hex_tmp); + UNMOCK(crypto_strongest_rand); } struct testcase_t hs_descriptor[] = { @@ -891,9 +956,8 @@ struct testcase_t hs_descriptor[] = { NULL, NULL }, { "desc_signature", test_desc_signature, TT_FORK, NULL, NULL }, - - { "parse_hs_desc_superencrypted", test_parse_hs_desc_superencrypted, - TT_FORK, NULL, NULL }, + { "build_authorized_client", test_build_authorized_client, TT_FORK, + NULL, NULL }, END_OF_TESTCASES }; diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index f8a465629a..6a061eaea4 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -34,6 +34,7 @@ #include "core/or/circuitlist.h" #include "core/or/circuituse.h" #include "lib/crypt_ops/crypto_rand.h" +#include "lib/fs/dir.h" #include "feature/dirauth/dirvote.h" #include "feature/nodelist/networkstatus.h" #include "feature/nodelist/nodelist.h" @@ -65,6 +66,13 @@ /* Trunnel */ #include "trunnel/hs/cell_establish_intro.h" +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + static networkstatus_t mock_ns; static networkstatus_t * @@ -220,6 +228,40 @@ helper_create_origin_circuit(int purpose, int flags) return circ; } +/* Helper: Return a newly allocated authorized client object with + * and a newly generated public key. */ +static hs_service_authorized_client_t * +helper_create_authorized_client(void) +{ + int ret; + hs_service_authorized_client_t *client; + curve25519_secret_key_t seckey; + client = tor_malloc_zero(sizeof(hs_service_authorized_client_t)); + + ret = curve25519_secret_key_generate(&seckey, 0); + tt_int_op(ret, OP_EQ, 0); + curve25519_public_key_generate(&client->client_pk, &seckey); + + done: + return client; +} + +/* Helper: Return a newly allocated authorized client object with the + * same client name and the same public key as the given client. */ +static hs_service_authorized_client_t * +helper_clone_authorized_client(const hs_service_authorized_client_t *client) +{ + hs_service_authorized_client_t *client_out; + + tor_assert(client); + + client_out = tor_malloc_zero(sizeof(hs_service_authorized_client_t)); + memcpy(client_out->client_pk.public_key, + client->client_pk.public_key, CURVE25519_PUBKEY_LEN); + + return client_out; +} + /* Helper: Return a newly allocated service object with the identity keypair * sets and the current descriptor. Then register it to the global map. * Caller should us hs_free_all() to free this service or remove it from the @@ -244,6 +286,26 @@ helper_create_service(void) return service; } +/* Helper: Return a newly allocated service object with clients. */ +static hs_service_t * +helper_create_service_with_clients(int num_clients) +{ + int i; + hs_service_t *service = helper_create_service(); + tt_assert(service); + service->config.is_client_auth_enabled = 1; + service->config.clients = smartlist_new(); + + for (i = 0; i < num_clients; i++) { + hs_service_authorized_client_t *client; + client = helper_create_authorized_client(); + smartlist_add(service->config.clients, client); + } + + done: + return service; +} + /* Helper: Return a newly allocated service intro point with two link * specifiers, one IPv4 and one legacy ID set to As. */ static hs_service_intro_point_t * @@ -303,6 +365,8 @@ test_load_keys(void *arg) /* It's in staging? */ tt_int_op(get_hs_service_staging_list_size(), OP_EQ, 1); +#undef conf_fmt + /* Load the keys for these. After that, the v3 service should be registered * in the global map. */ hs_service_load_all_keys(); @@ -322,6 +386,9 @@ test_load_keys(void *arg) tt_int_op(hs_address_is_valid(addr), OP_EQ, 1); tt_str_op(addr, OP_EQ, s->onion_address); + /* Check that the is_client_auth_enabled is not set. */ + tt_assert(!s->config.is_client_auth_enabled); + done: tor_free(hsdir_v2); tor_free(hsdir_v3); @@ -329,6 +396,184 @@ test_load_keys(void *arg) } static void +test_client_filename_is_valid(void *arg) +{ + (void) arg; + + /* Valid file name. */ + tt_assert(client_filename_is_valid("a.auth")); + /* Valid file name with special character. */ + tt_assert(client_filename_is_valid("a-.auth")); + /* Invalid extension. */ + tt_assert(!client_filename_is_valid("a.ath")); + /* Nothing before the extension. */ + tt_assert(!client_filename_is_valid(".auth")); + + done: + ; +} + +static void +test_parse_authorized_client(void *arg) +{ + hs_service_authorized_client_t *client = NULL; + + (void) arg; + + /* Valid authorized client. */ + client = parse_authorized_client( + "descriptor:x25519:dz4q5xqlb4ldnbs72iarrml4ephk3du4i7o2cgiva5lwr6wkquja"); + tt_assert(client); + + /* Wrong number of fields. */ + tt_assert(!parse_authorized_client("a:b:c:d:e")); + /* Wrong auth type. */ + tt_assert(!parse_authorized_client( + "x:x25519:dz4q5xqlb4ldnbs72iarrml4ephk3du4i7o2cgiva5lwr6wkquja")); + /* Wrong key type. */ + tt_assert(!parse_authorized_client( + "descriptor:x:dz4q5xqlb4ldnbs72iarrml4ephk3du4i7o2cgiva5lwr6wkquja")); + /* Some malformed string. */ + tt_assert(!parse_authorized_client("descriptor:x25519:aa==")); + tt_assert(!parse_authorized_client("descriptor:")); + tt_assert(!parse_authorized_client("descriptor:x25519")); + tt_assert(!parse_authorized_client("descriptor:x25519:")); + tt_assert(!parse_authorized_client("")); + + done: + service_authorized_client_free(client); +} + +static char * +mock_read_file_to_str(const char *filename, int flags, struct stat *stat_out) +{ + char *ret = NULL; + + (void) flags; + (void) stat_out; + + if (!strcmp(filename, get_fname("hs3" PATH_SEPARATOR + "authorized_clients" PATH_SEPARATOR + "client1.auth"))) { + ret = tor_strdup("descriptor:x25519:" + "dz4q5xqlb4ldnbs72iarrml4ephk3du4i7o2cgiva5lwr6wkquja"); + goto done; + } + + if (!strcmp(filename, get_fname("hs3" PATH_SEPARATOR + "authorized_clients" PATH_SEPARATOR + "dummy.xxx"))) { + ret = tor_strdup("descriptor:x25519:" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + goto done; + } + + if (!strcmp(filename, get_fname("hs3" PATH_SEPARATOR + "authorized_clients" PATH_SEPARATOR + "client2.auth"))) { + ret = tor_strdup("descriptor:x25519:" + "okoi2gml3wd6x7jganlk5d66xxyjgg24sxw4y7javx4giqr66zta"); + goto done; + } + + done: + return ret; +} + +static smartlist_t * +mock_tor_listdir(const char *dirname) +{ + smartlist_t *file_list = smartlist_new(); + + (void) dirname; + + smartlist_add(file_list, tor_strdup("client1.auth")); + smartlist_add(file_list, tor_strdup("dummy.xxx")); + smartlist_add(file_list, tor_strdup("client2.auth")); + + return file_list; +} + +static void +test_load_keys_with_client_auth(void *arg) +{ + int ret; + char *conf = NULL; + smartlist_t *pubkey_b32_list = smartlist_new(); + char *hsdir_v3 = tor_strdup(get_fname("hs3")); + hs_service_t *service; + + (void) arg; + + hs_init(); + smartlist_add(pubkey_b32_list, tor_strdup( + "dz4q5xqlb4ldnbs72iarrml4ephk3du4i7o2cgiva5lwr6wkquja")); + smartlist_add(pubkey_b32_list, tor_strdup( + "okoi2gml3wd6x7jganlk5d66xxyjgg24sxw4y7javx4giqr66zta")); + +#define conf_fmt \ + "HiddenServiceDir %s\n" \ + "HiddenServiceVersion %d\n" \ + "HiddenServicePort 65534\n" + + tor_asprintf(&conf, conf_fmt, hsdir_v3, HS_VERSION_THREE); + ret = helper_config_service(conf); + tor_free(conf); + tt_int_op(ret, OP_EQ, 0); + /* It's in staging? */ + tt_int_op(get_hs_service_staging_list_size(), OP_EQ, 1); + +#undef conf_fmt + + MOCK(read_file_to_str, mock_read_file_to_str); + MOCK(tor_listdir, mock_tor_listdir); + + /* Load the keys for these. After that, the v3 service should be registered + * in the global map. */ + hs_service_load_all_keys(); + tt_int_op(get_hs_service_map_size(), OP_EQ, 1); + + service = get_first_service(); + tt_assert(service->config.clients); + tt_int_op(smartlist_len(service->config.clients), OP_EQ, + smartlist_len(pubkey_b32_list)); + + /* Test that the is_client_auth_enabled flag is set. */ + tt_assert(service->config.is_client_auth_enabled); + + /* Test that the keys in clients are correct. */ + SMARTLIST_FOREACH_BEGIN(pubkey_b32_list, char *, pubkey_b32) { + + curve25519_public_key_t pubkey; + /* This flag will be set if the key is found in clients. */ + int is_found = 0; + base32_decode((char *) pubkey.public_key, sizeof(pubkey.public_key), + pubkey_b32, strlen(pubkey_b32)); + + SMARTLIST_FOREACH_BEGIN(service->config.clients, + hs_service_authorized_client_t *, client) { + if (tor_memeq(&pubkey, &client->client_pk, sizeof(pubkey))) { + is_found = 1; + break; + } + } SMARTLIST_FOREACH_END(client); + + tt_assert(is_found); + + } SMARTLIST_FOREACH_END(pubkey_b32); + + done: + if (pubkey_b32_list) { + SMARTLIST_FOREACH(pubkey_b32_list, char *, s, tor_free(s)); + } + smartlist_free(pubkey_b32_list); + tor_free(hsdir_v3); + hs_free_all(); + UNMOCK(read_file_to_str); + UNMOCK(tor_listdir); +} + +static void test_access_service(void *arg) { int ret; @@ -1371,6 +1616,90 @@ test_build_update_descriptors(void *arg) nodelist_free_all(); } +/** Test building descriptors. We use this separate function instead of + * using test_build_update_descriptors because that function is too complex + * and also too interactive. */ +static void +test_build_descriptors(void *arg) +{ + int ret; + time_t now = time(NULL); + + (void) arg; + + hs_init(); + + MOCK(get_or_state, + get_or_state_replacement); + MOCK(networkstatus_get_live_consensus, + mock_networkstatus_get_live_consensus); + + dummy_state = tor_malloc_zero(sizeof(or_state_t)); + + ret = parse_rfc1123_time("Sat, 26 Oct 1985 03:00:00 UTC", + &mock_ns.valid_after); + tt_int_op(ret, OP_EQ, 0); + ret = parse_rfc1123_time("Sat, 26 Oct 1985 04:00:00 UTC", + &mock_ns.fresh_until); + tt_int_op(ret, OP_EQ, 0); + voting_schedule_recalculate_timing(get_options(), mock_ns.valid_after); + + /* Generate a valid number of fake auth clients when a client authorization + * is disabled. */ + { + hs_service_t *service = helper_create_service(); + service_descriptor_free(service->desc_current); + service->desc_current = NULL; + + build_all_descriptors(now); + hs_desc_superencrypted_data_t *superencrypted; + superencrypted = &service->desc_current->desc->superencrypted_data; + tt_int_op(smartlist_len(superencrypted->clients), OP_EQ, 16); + } + + /* Generate a valid number of fake auth clients when the number of + * clients is zero. */ + { + hs_service_t *service = helper_create_service_with_clients(0); + service_descriptor_free(service->desc_current); + service->desc_current = NULL; + + build_all_descriptors(now); + hs_desc_superencrypted_data_t *superencrypted; + superencrypted = &service->desc_current->desc->superencrypted_data; + tt_int_op(smartlist_len(superencrypted->clients), OP_EQ, 16); + } + + /* Generate a valid number of fake auth clients when the number of + * clients is not a multiple of 16. */ + { + hs_service_t *service = helper_create_service_with_clients(20); + service_descriptor_free(service->desc_current); + service->desc_current = NULL; + + build_all_descriptors(now); + hs_desc_superencrypted_data_t *superencrypted; + superencrypted = &service->desc_current->desc->superencrypted_data; + tt_int_op(smartlist_len(superencrypted->clients), OP_EQ, 32); + } + + /* Do not generate any fake desc client when the number of clients is + * a multiple of 16 but not zero. */ + { + hs_service_t *service = helper_create_service_with_clients(32); + service_descriptor_free(service->desc_current); + service->desc_current = NULL; + + build_all_descriptors(now); + hs_desc_superencrypted_data_t *superencrypted; + superencrypted = &service->desc_current->desc->superencrypted_data; + tt_int_op(smartlist_len(superencrypted->clients), OP_EQ, 32); + } + + done: + hs_free_all(); +} + static void test_upload_descriptors(void *arg) { @@ -1556,11 +1885,137 @@ test_rendezvous1_parsing(void *arg) UNMOCK(relay_send_command_from_edge_); } +static void +test_authorized_client_config_equal(void *arg) +{ + int ret; + hs_service_config_t *config1, *config2; + + (void) arg; + + config1 = tor_malloc_zero(sizeof(*config1)); + config2 = tor_malloc_zero(sizeof(*config2)); + + /* Both configs are empty. */ + { + config1->clients = smartlist_new(); + config2->clients = smartlist_new(); + + ret = service_authorized_client_config_equal(config1, config2); + tt_int_op(ret, OP_EQ, 1); + + service_clear_config(config1); + service_clear_config(config2); + } + + /* Both configs have exactly the same client config. */ + { + config1->clients = smartlist_new(); + config2->clients = smartlist_new(); + + hs_service_authorized_client_t *client1, *client2; + client1 = helper_create_authorized_client(); + client2 = helper_create_authorized_client(); + + smartlist_add(config1->clients, client1); + smartlist_add(config1->clients, client2); + + /* We should swap the order of clients here to test that the order + * does not matter. */ + smartlist_add(config2->clients, helper_clone_authorized_client(client2)); + smartlist_add(config2->clients, helper_clone_authorized_client(client1)); + + ret = service_authorized_client_config_equal(config1, config2); + tt_int_op(ret, OP_EQ, 1); + + service_clear_config(config1); + service_clear_config(config2); + } + + /* The numbers of clients in both configs are not equal. */ + { + config1->clients = smartlist_new(); + config2->clients = smartlist_new(); + + hs_service_authorized_client_t *client1, *client2; + client1 = helper_create_authorized_client(); + client2 = helper_create_authorized_client(); + + smartlist_add(config1->clients, client1); + smartlist_add(config1->clients, client2); + + smartlist_add(config2->clients, helper_clone_authorized_client(client1)); + + ret = service_authorized_client_config_equal(config1, config2); + tt_int_op(ret, OP_EQ, 0); + + service_clear_config(config1); + service_clear_config(config2); + } + + /* The first config has two distinct clients while the second config + * has two clients but they are duplicate. */ + { + config1->clients = smartlist_new(); + config2->clients = smartlist_new(); + + hs_service_authorized_client_t *client1, *client2; + client1 = helper_create_authorized_client(); + client2 = helper_create_authorized_client(); + + smartlist_add(config1->clients, client1); + smartlist_add(config1->clients, client2); + + smartlist_add(config2->clients, helper_clone_authorized_client(client1)); + smartlist_add(config2->clients, helper_clone_authorized_client(client1)); + + ret = service_authorized_client_config_equal(config1, config2); + tt_int_op(ret, OP_EQ, 0); + + service_clear_config(config1); + service_clear_config(config2); + } + + /* Both configs have totally distinct clients. */ + { + config1->clients = smartlist_new(); + config2->clients = smartlist_new(); + + hs_service_authorized_client_t *client1, *client2, *client3, *client4; + client1 = helper_create_authorized_client(); + client2 = helper_create_authorized_client(); + client3 = helper_create_authorized_client(); + client4 = helper_create_authorized_client(); + + smartlist_add(config1->clients, client1); + smartlist_add(config1->clients, client2); + + smartlist_add(config2->clients, client3); + smartlist_add(config2->clients, client4); + + ret = service_authorized_client_config_equal(config1, config2); + tt_int_op(ret, OP_EQ, 0); + + service_clear_config(config1); + service_clear_config(config2); + } + + done: + tor_free(config1); + tor_free(config2); +} + struct testcase_t hs_service_tests[] = { { "e2e_rend_circuit_setup", test_e2e_rend_circuit_setup, TT_FORK, NULL, NULL }, { "load_keys", test_load_keys, TT_FORK, NULL, NULL }, + { "client_filename_is_valid", test_client_filename_is_valid, TT_FORK, + NULL, NULL }, + { "parse_authorized_client", test_parse_authorized_client, TT_FORK, + NULL, NULL }, + { "load_keys_with_client_auth", test_load_keys_with_client_auth, TT_FORK, + NULL, NULL }, { "access_service", test_access_service, TT_FORK, NULL, NULL }, { "service_intro_point", test_service_intro_point, TT_FORK, @@ -1583,10 +2038,14 @@ struct testcase_t hs_service_tests[] = { NULL, NULL }, { "build_update_descriptors", test_build_update_descriptors, TT_FORK, NULL, NULL }, + { "build_descriptors", test_build_descriptors, TT_FORK, + NULL, NULL }, { "upload_descriptors", test_upload_descriptors, TT_FORK, NULL, NULL }, { "rendezvous1_parsing", test_rendezvous1_parsing, TT_FORK, NULL, NULL }, + { "authorized_client_config_equal", test_authorized_client_config_equal, + TT_FORK, NULL, NULL }, END_OF_TESTCASES }; diff --git a/src/test/testing_common.c b/src/test/testing_common.c index 42b5190ca0..5d4c2f15af 100644 --- a/src/test/testing_common.c +++ b/src/test/testing_common.c @@ -113,8 +113,8 @@ get_fname_suffix(const char *name, const char *suffix) setup_directory(); if (!name) return temp_dir; - tor_snprintf(buf,sizeof(buf),"%s/%s%s%s",temp_dir,name,suffix ? "_" : "", - suffix ? suffix : ""); + tor_snprintf(buf,sizeof(buf),"%s%s%s%s%s", temp_dir, PATH_SEPARATOR, name, + suffix ? "_" : "", suffix ? suffix : ""); return buf; } |