diff options
author | Nick Mathewson <nickm@torproject.org> | 2018-09-07 15:03:32 -0400 |
---|---|---|
committer | Nick Mathewson <nickm@torproject.org> | 2018-09-07 15:03:32 -0400 |
commit | 9ca1af9a8757ee73cf4018263cb3ae2340ebaa9f (patch) | |
tree | c747d03a54dde4ad4defa2c4c129545aa31064b1 /src/feature | |
parent | 13d0855a893a46a0f6dc06dc7d983ea321f7206a (diff) | |
parent | 3695ef6343fa1c05cd15a3ddf35c3fe6991ff2ad (diff) | |
download | tor-9ca1af9a8757ee73cf4018263cb3ae2340ebaa9f.tar.gz tor-9ca1af9a8757ee73cf4018263cb3ae2340ebaa9f.zip |
Merge remote-tracking branch 'dgoulet/ticket20700_035_03'
Diffstat (limited to 'src/feature')
-rw-r--r-- | src/feature/hs/hs_client.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 |
8 files changed, 1689 insertions, 320 deletions
diff --git a/src/feature/hs/hs_client.c b/src/feature/hs/hs_client.c index 1f9218e15a..6f031eb3b9 100644 --- a/src/feature/hs/hs_client.c +++ b/src/feature/hs/hs_client.c @@ -42,6 +42,10 @@ #include "core/or/extend_info_st.h" #include "core/or/origin_circuit_st.h" +/* Client-side authorizations for hidden services; map of service identity + * public key to hs_client_service_authorization_t *. */ +static digest256map_t *client_auths = NULL; + /* Return a human-readable string for the client fetch status code. */ static const char * fetch_status_to_string(hs_client_fetch_status_t status) @@ -1177,6 +1181,19 @@ can_client_refetch_desc(const ed25519_public_key_t *identity_pk, return 0; } +/* Return the client auth in the map using the service identity public key. + * Return NULL if it does not exist in the map. */ +static hs_client_service_authorization_t * +find_client_auth(const ed25519_public_key_t *service_identity_pk) +{ + /* If the map is not allocated, we can assume that we do not have any client + * auth information. */ + if (!client_auths) { + return NULL; + } + return digest256map_get(client_auths, service_identity_pk->pubkey); +} + /* ========== */ /* Public API */ /* ========== */ @@ -1215,11 +1232,19 @@ hs_client_decode_descriptor(const char *desc_str, int ret; uint8_t subcredential[DIGEST256_LEN]; ed25519_public_key_t blinded_pubkey; + hs_client_service_authorization_t *client_auth = NULL; + curve25519_secret_key_t *client_auht_sk = NULL; tor_assert(desc_str); tor_assert(service_identity_pk); tor_assert(desc); + /* Check if we have a client authorization for this service in the map. */ + client_auth = find_client_auth(service_identity_pk); + if (client_auth) { + client_auht_sk = &client_auth->enc_seckey; + } + /* Create subcredential for this HS so that we can decrypt */ { uint64_t current_time_period = hs_get_time_period_num(0); @@ -1229,7 +1254,8 @@ hs_client_decode_descriptor(const char *desc_str, } /* Parse descriptor */ - ret = hs_desc_decode_descriptor(desc_str, subcredential, desc); + ret = hs_desc_decode_descriptor(desc_str, subcredential, + client_auht_sk, desc); memwipe(subcredential, 0, sizeof(subcredential)); if (ret < 0) { log_warn(LD_GENERAL, "Could not parse received descriptor as client."); @@ -1393,6 +1419,233 @@ hs_client_receive_rendezvous_acked(origin_circuit_t *circ, return -1; } +#define client_service_authorization_free(auth) \ + FREE_AND_NULL(hs_client_service_authorization_t, \ + client_service_authorization_free_, (auth)) + +static void +client_service_authorization_free_(hs_client_service_authorization_t *auth) +{ + if (auth) { + memwipe(auth, 0, sizeof(*auth)); + } + tor_free(auth); +} + +/** Helper for digest256map_free. */ +static void +client_service_authorization_free_void(void *auth) +{ + client_service_authorization_free_(auth); +} + +static void +client_service_authorization_free_all(void) +{ + if (!client_auths) { + return; + } + digest256map_free(client_auths, client_service_authorization_free_void); +} + +/* Check if the auth key file name is valid or not. Return 1 if valid, + * otherwise return 0. */ +STATIC int +auth_key_filename_is_valid(const char *filename) +{ + int ret = 1; + const char *valid_extension = ".auth_private"; + + tor_assert(filename); + + /* The length of the filename must be greater than the length of the + * extension and the valid extension must be at the end of filename. */ + if (!strcmpend(filename, valid_extension) && + strlen(filename) != strlen(valid_extension)) { + ret = 1; + } else { + ret = 0; + } + + return ret; +} + +STATIC hs_client_service_authorization_t * +parse_auth_file_content(const char *client_key_str) +{ + char *onion_address = NULL; + char *auth_type = NULL; + char *key_type = NULL; + char *seckey_b32 = NULL; + hs_client_service_authorization_t *auth = NULL; + smartlist_t *fields = smartlist_new(); + + tor_assert(client_key_str); + + smartlist_split_string(fields, client_key_str, ":", + SPLIT_SKIP_SPACE, 0); + /* Wrong number of fields. */ + if (smartlist_len(fields) != 4) { + goto err; + } + + onion_address = smartlist_get(fields, 0); + auth_type = smartlist_get(fields, 1); + key_type = smartlist_get(fields, 2); + seckey_b32 = smartlist_get(fields, 3); + + /* Currently, the only supported auth type is "descriptor" and the only + * supported key type is "x25519". */ + if (strcmp(auth_type, "descriptor") || strcmp(key_type, "x25519")) { + goto err; + } + + if (strlen(seckey_b32) != BASE32_NOPAD_LEN(CURVE25519_PUBKEY_LEN)) { + log_warn(LD_REND, "Client authorization encoded base32 private key " + "length is invalid: %s", seckey_b32); + goto err; + } + + auth = tor_malloc_zero(sizeof(hs_client_service_authorization_t)); + if (base32_decode((char *) auth->enc_seckey.secret_key, + sizeof(auth->enc_seckey.secret_key), + seckey_b32, strlen(seckey_b32)) < 0) { + goto err; + } + strncpy(auth->onion_address, onion_address, HS_SERVICE_ADDR_LEN_BASE32); + + /* Success. */ + goto done; + + err: + client_service_authorization_free(auth); + done: + /* It is also a good idea to wipe the private key. */ + if (seckey_b32) { + memwipe(seckey_b32, 0, strlen(seckey_b32)); + } + if (fields) { + SMARTLIST_FOREACH(fields, char *, s, tor_free(s)); + smartlist_free(fields); + } + return auth; +} + +/* From a set of <b>options</b>, setup every client authorization detail + * found. Return 0 on success or -1 on failure. If <b>validate_only</b> + * is set, parse, warn and return as normal, but don't actually change + * the configuration. */ +int +hs_config_client_authorization(const or_options_t *options, + int validate_only) +{ + int ret = -1; + digest256map_t *auths = digest256map_new(); + char *key_dir = NULL; + smartlist_t *file_list = NULL; + char *client_key_str = NULL; + char *client_key_file_path = NULL; + + tor_assert(options); + + /* There is no client auth configured. We can just silently ignore this + * function. */ + if (!options->ClientOnionAuthDir) { + ret = 0; + goto end; + } + + key_dir = tor_strdup(options->ClientOnionAuthDir); + + /* Make sure the directory exists and is private enough. */ + if (check_private_dir(key_dir, 0, options->User) < 0) { + goto end; + } + + file_list = tor_listdir(key_dir); + if (file_list == NULL) { + log_warn(LD_REND, "Client authorization key directory %s can't be listed.", + key_dir); + goto end; + } + + SMARTLIST_FOREACH_BEGIN(file_list, char *, filename) { + + hs_client_service_authorization_t *auth = NULL; + ed25519_public_key_t identity_pk; + log_info(LD_REND, "Loading a client authorization key file %s...", + filename); + + if (!auth_key_filename_is_valid(filename)) { + log_notice(LD_REND, "Client authorization unrecognized filename %s. " + "File must end in .auth_private. Ignoring.", + filename); + continue; + } + + /* Create a full path for a file. */ + client_key_file_path = hs_path_from_filename(key_dir, filename); + client_key_str = read_file_to_str(client_key_file_path, 0, NULL); + /* Free the file path immediately after using it. */ + tor_free(client_key_file_path); + + /* If we cannot read the file, continue with the next file. */ + if (!client_key_str) { + log_warn(LD_REND, "The file %s cannot be read.", filename); + continue; + } + + auth = parse_auth_file_content(client_key_str); + /* Free immediately after using it. */ + tor_free(client_key_str); + + if (auth) { + /* Parse the onion address to get an identity public key and use it + * as a key of global map in the future. */ + if (hs_parse_address(auth->onion_address, &identity_pk, + NULL, NULL) < 0) { + client_service_authorization_free(auth); + log_warn(LD_REND, "The onion address \"%s\" is invalid in " + "file %s", filename, auth->onion_address); + continue; + } + + if (digest256map_get(auths, identity_pk.pubkey)) { + client_service_authorization_free(auth); + log_warn(LD_REND, "Duplicate authorization for the same hidden " + "service address %s.", + safe_str_client(auth->onion_address)); + goto end; + } + + digest256map_set(auths, identity_pk.pubkey, auth); + log_info(LD_REND, "Loaded a client authorization key file %s.", + filename); + } + } SMARTLIST_FOREACH_END(filename); + + /* Success. */ + ret = 0; + + end: + tor_free(key_dir); + tor_free(client_key_str); + tor_free(client_key_file_path); + if (file_list) { + SMARTLIST_FOREACH(file_list, char *, s, tor_free(s)); + smartlist_free(file_list); + } + + if (!validate_only && ret == 0) { + client_service_authorization_free_all(); + client_auths = auths; + } else { + digest256map_free(auths, client_service_authorization_free_void); + } + + return ret; +} + /* This is called when a descriptor has arrived following a fetch request and * has been stored in the client cache. Every entry connection that matches * the service identity key in the ident will get attached to the hidden @@ -1589,6 +1842,7 @@ hs_client_free_all(void) { /* Purge the hidden service request cache. */ hs_purge_last_hid_serv_requests(); + client_service_authorization_free_all(); } /* Purge all potentially remotely-detectable state held in the hidden @@ -1621,3 +1875,13 @@ hs_client_dir_info_changed(void) * AP_CONN_STATE_RENDDESC_WAIT state in order to fetch the descriptor. */ retry_all_socks_conn_waiting_for_desc(); } + +#ifdef TOR_UNIT_TESTS + +STATIC digest256map_t * +get_hs_client_auths_map(void) +{ + return client_auths; +} + +#endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/feature/hs/hs_client.h b/src/feature/hs/hs_client.h index 6ee9f40c00..1ba0338dc3 100644 --- a/src/feature/hs/hs_client.h +++ b/src/feature/hs/hs_client.h @@ -31,6 +31,16 @@ typedef enum { HS_CLIENT_FETCH_PENDING = 5, } hs_client_fetch_status_t; +/** Client-side configuration of authorization for a service. */ +typedef struct hs_client_service_authorization_t { + /* An curve25519 secret key used to compute decryption keys that + * allow the client to decrypt the hidden service descriptor. */ + curve25519_secret_key_t enc_seckey; + + /* An onion address that is used to connect to the onion service. */ + char onion_address[HS_SERVICE_ADDR_LEN_BASE32+1]; +} hs_client_service_authorization_t; + void hs_client_note_connection_attempt_succeeded( const edge_connection_t *conn); @@ -63,6 +73,9 @@ void hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident); extend_info_t *hs_client_get_random_intro_from_edge( const edge_connection_t *edge_conn); +int hs_config_client_authorization(const or_options_t *options, + int validate_only); + int hs_client_reextend_intro_circuit(origin_circuit_t *circ); void hs_client_purge_state(void); @@ -71,6 +84,11 @@ void hs_client_free_all(void); #ifdef HS_CLIENT_PRIVATE +STATIC int auth_key_filename_is_valid(const char *filename); + +STATIC hs_client_service_authorization_t * +parse_auth_file_content(const char *client_key_str); + STATIC routerstatus_t * pick_hsdir_v3(const ed25519_public_key_t *onion_identity_pk); @@ -86,6 +104,12 @@ STATIC int handle_rendezvous2(origin_circuit_t *circ, const uint8_t *payload, MOCK_DECL(STATIC hs_client_fetch_status_t, fetch_v3_desc, (const ed25519_public_key_t *onion_identity_pk)); +#ifdef TOR_UNIT_TESTS + +STATIC digest256map_t *get_hs_client_auths_map(void); + +#endif /* defined(TOR_UNIT_TESTS) */ + #endif /* defined(HS_CLIENT_PRIVATE) */ #endif /* !defined(TOR_HS_CLIENT_H) */ diff --git a/src/feature/hs/hs_config.c b/src/feature/hs/hs_config.c index e972576482..eaeb58829a 100644 --- a/src/feature/hs/hs_config.c +++ b/src/feature/hs/hs_config.c @@ -27,7 +27,9 @@ #include "feature/hs/hs_common.h" #include "feature/hs/hs_config.h" +#include "feature/hs/hs_client.h" #include "feature/hs/hs_service.h" +#include "feature/rend/rendclient.h" #include "feature/rend/rendservice.h" #include "lib/encoding/confline.h" #include "app/config/or_options_st.h" @@ -613,3 +615,28 @@ hs_config_service_all(const or_options_t *options, int validate_only) /* Tor main should call the free all function on error. */ return ret; } + +/* From a set of <b>options</b>, setup every client authorization found. + * Return 0 on success or -1 on failure. If <b>validate_only</b> is set, + * parse, warn and return as normal, but don't actually change the + * configured state. */ +int +hs_config_client_auth_all(const or_options_t *options, int validate_only) +{ + int ret = -1; + + /* Configure v2 authorization. */ + if (rend_parse_service_authorization(options, validate_only) < 0) { + goto done; + } + + /* Configure v3 authorization. */ + if (hs_config_client_authorization(options, validate_only) < 0) { + goto done; + } + + /* Success. */ + ret = 0; + done: + return ret; +} diff --git a/src/feature/hs/hs_config.h b/src/feature/hs/hs_config.h index 96eb19ee70..f443e814c4 100644 --- a/src/feature/hs/hs_config.h +++ b/src/feature/hs/hs_config.h @@ -19,6 +19,7 @@ /* API */ int hs_config_service_all(const or_options_t *options, int validate_only); +int hs_config_client_auth_all(const or_options_t *options, int validate_only); #endif /* !defined(TOR_HS_CONFIG_H) */ diff --git a/src/feature/hs/hs_descriptor.c b/src/feature/hs/hs_descriptor.c index 3928000164..d0cdffdf10 100644 --- a/src/feature/hs/hs_descriptor.c +++ b/src/feature/hs/hs_descriptor.c @@ -152,42 +152,6 @@ static token_rule_t hs_desc_intro_point_v3_token_table[] = { END_OF_TABLE }; -/* Free the content of the plaintext section of a descriptor. */ -STATIC void -desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc) -{ - if (!desc) { - return; - } - - if (desc->superencrypted_blob) { - tor_free(desc->superencrypted_blob); - } - tor_cert_free(desc->signing_key_cert); - - memwipe(desc, 0, sizeof(*desc)); -} - -/* Free the content of the encrypted section of a descriptor. */ -static void -desc_encrypted_data_free_contents(hs_desc_encrypted_data_t *desc) -{ - if (!desc) { - return; - } - - if (desc->intro_auth_types) { - SMARTLIST_FOREACH(desc->intro_auth_types, char *, a, tor_free(a)); - smartlist_free(desc->intro_auth_types); - } - if (desc->intro_points) { - SMARTLIST_FOREACH(desc->intro_points, hs_desc_intro_point_t *, ip, - hs_desc_intro_point_free(ip)); - smartlist_free(desc->intro_points); - } - memwipe(desc, 0, sizeof(*desc)); -} - /* Using a key, salt and encrypted payload, build a MAC and put it in mac_out. * We use SHA3-256 for the MAC computation. * This function can't fail. */ @@ -220,53 +184,72 @@ build_mac(const uint8_t *mac_key, size_t mac_key_len, crypto_digest_free(digest); } -/* Using a given decriptor object, build the secret input needed for the - * KDF and put it in the dst pointer which is an already allocated buffer - * of size dstlen. */ -static void -build_secret_input(const hs_descriptor_t *desc, uint8_t *dst, size_t dstlen) +/* Using a secret data and a given decriptor object, build the secret + * input needed for the KDF. + * + * secret_input = SECRET_DATA | subcredential | INT_8(revision_counter) + * + * Then, set the newly allocated buffer in secret_input_out and return the + * length of the buffer. */ +static size_t +build_secret_input(const hs_descriptor_t *desc, + const uint8_t *secret_data, + size_t secret_data_len, + uint8_t **secret_input_out) { size_t offset = 0; + size_t secret_input_len = secret_data_len + DIGEST256_LEN + sizeof(uint64_t); + uint8_t *secret_input = NULL; tor_assert(desc); - tor_assert(dst); - tor_assert(HS_DESC_ENCRYPTED_SECRET_INPUT_LEN <= dstlen); - - /* XXX use the destination length as the memcpy length */ - /* Copy blinded public key. */ - memcpy(dst, desc->plaintext_data.blinded_pubkey.pubkey, - sizeof(desc->plaintext_data.blinded_pubkey.pubkey)); - offset += sizeof(desc->plaintext_data.blinded_pubkey.pubkey); + tor_assert(secret_data); + tor_assert(secret_input_out); + + secret_input = tor_malloc_zero(secret_input_len); + + /* Copy the secret data. */ + memcpy(secret_input, secret_data, secret_data_len); + offset += secret_data_len; /* Copy subcredential. */ - memcpy(dst + offset, desc->subcredential, sizeof(desc->subcredential)); - offset += sizeof(desc->subcredential); + memcpy(secret_input + offset, desc->subcredential, DIGEST256_LEN); + offset += DIGEST256_LEN; /* Copy revision counter value. */ - set_uint64(dst + offset, tor_htonll(desc->plaintext_data.revision_counter)); + set_uint64(secret_input + offset, + tor_htonll(desc->plaintext_data.revision_counter)); offset += sizeof(uint64_t); - tor_assert(HS_DESC_ENCRYPTED_SECRET_INPUT_LEN == offset); + tor_assert(secret_input_len == offset); + + *secret_input_out = secret_input; + + return secret_input_len; } /* Do the KDF construction and put the resulting data in key_out which is of * key_out_len length. It uses SHAKE-256 as specified in the spec. */ static void build_kdf_key(const hs_descriptor_t *desc, + const uint8_t *secret_data, + size_t secret_data_len, const uint8_t *salt, size_t salt_len, uint8_t *key_out, size_t key_out_len, int is_superencrypted_layer) { - uint8_t secret_input[HS_DESC_ENCRYPTED_SECRET_INPUT_LEN]; + uint8_t *secret_input = NULL; + size_t secret_input_len; crypto_xof_t *xof; tor_assert(desc); + tor_assert(secret_data); tor_assert(salt); tor_assert(key_out); /* Build the secret input for the KDF computation. */ - build_secret_input(desc, secret_input, sizeof(secret_input)); + secret_input_len = build_secret_input(desc, secret_data, + secret_data_len, &secret_input); xof = crypto_xof_new(); /* Feed our KDF. [SHAKE it like a polaroid picture --Yawning]. */ - crypto_xof_add_bytes(xof, secret_input, sizeof(secret_input)); + crypto_xof_add_bytes(xof, secret_input, secret_input_len); crypto_xof_add_bytes(xof, salt, salt_len); /* Feed in the right string constant based on the desc layer */ @@ -281,14 +264,18 @@ build_kdf_key(const hs_descriptor_t *desc, /* Eat from our KDF. */ crypto_xof_squeeze_bytes(xof, key_out, key_out_len); crypto_xof_free(xof); - memwipe(secret_input, 0, sizeof(secret_input)); + memwipe(secret_input, 0, secret_input_len); + + tor_free(secret_input); } -/* Using the given descriptor and salt, run it through our KDF function and - * then extract a secret key in key_out, the IV in iv_out and MAC in mac_out. - * This function can't fail. */ +/* Using the given descriptor, secret data, and salt, run it through our + * KDF function and then extract a secret key in key_out, the IV in iv_out + * and MAC in mac_out. This function can't fail. */ static void build_secret_key_iv_mac(const hs_descriptor_t *desc, + const uint8_t *secret_data, + size_t secret_data_len, const uint8_t *salt, size_t salt_len, uint8_t *key_out, size_t key_len, uint8_t *iv_out, size_t iv_len, @@ -299,12 +286,14 @@ build_secret_key_iv_mac(const hs_descriptor_t *desc, uint8_t kdf_key[HS_DESC_ENCRYPTED_KDF_OUTPUT_LEN]; tor_assert(desc); + tor_assert(secret_data); tor_assert(salt); tor_assert(key_out); tor_assert(iv_out); tor_assert(mac_out); - build_kdf_key(desc, salt, salt_len, kdf_key, sizeof(kdf_key), + build_kdf_key(desc, secret_data, secret_data_len, + salt, salt_len, kdf_key, sizeof(kdf_key), is_superencrypted_layer); /* Copy the bytes we need for both the secret key and IV. */ memcpy(key_out, kdf_key, key_len); @@ -610,12 +599,15 @@ build_encrypted(const uint8_t *key, const uint8_t *iv, const char *plaintext, return encrypted_len; } -/* Encrypt the given <b>plaintext</b> buffer using <b>desc</b> to get the - * keys. Set encrypted_out with the encrypted data and return the length of - * it. <b>is_superencrypted_layer</b> is set if this is the outer encrypted - * layer of the descriptor. */ +/* Encrypt the given <b>plaintext</b> buffer using <b>desc</b> and + * <b>secret_data</b> to get the keys. Set encrypted_out with the encrypted + * data and return the length of it. <b>is_superencrypted_layer</b> is set + * if this is the outer encrypted layer of the descriptor. */ static size_t -encrypt_descriptor_data(const hs_descriptor_t *desc, const char *plaintext, +encrypt_descriptor_data(const hs_descriptor_t *desc, + const uint8_t *secret_data, + size_t secret_data_len, + const char *plaintext, char **encrypted_out, int is_superencrypted_layer) { char *final_blob; @@ -626,6 +618,7 @@ encrypt_descriptor_data(const hs_descriptor_t *desc, const char *plaintext, uint8_t mac_key[DIGEST256_LEN], mac[DIGEST256_LEN]; tor_assert(desc); + tor_assert(secret_data); tor_assert(plaintext); tor_assert(encrypted_out); @@ -634,7 +627,8 @@ encrypt_descriptor_data(const hs_descriptor_t *desc, const char *plaintext, /* KDF construction resulting in a key from which the secret key, IV and MAC * key are extracted which is what we need for the encryption. */ - build_secret_key_iv_mac(desc, salt, sizeof(salt), + build_secret_key_iv_mac(desc, secret_data, secret_data_len, + salt, sizeof(salt), secret_key, sizeof(secret_key), secret_iv, sizeof(secret_iv), mac_key, sizeof(mac_key), @@ -675,69 +669,65 @@ encrypt_descriptor_data(const hs_descriptor_t *desc, const char *plaintext, return final_blob_len; } -/* Create and return a string containing a fake client-auth entry. It's the - * responsibility of the caller to free the returned string. This function will - * never fail. */ +/* Create and return a string containing a client-auth entry. It's the + * responsibility of the caller to free the returned string. This function + * will never fail. */ static char * -get_fake_auth_client_str(void) +get_auth_client_str(const hs_desc_authorized_client_t *client) { + int ret; char *auth_client_str = NULL; - /* We are gonna fill these arrays with fake base64 data. They are all double + /* We are gonna fill these arrays with base64 data. They are all double * the size of their binary representation to fit the base64 overhead. */ - char client_id_b64[8*2]; - char iv_b64[16*2]; - char encrypted_cookie_b64[16*2]; - int retval; - - /* This is a macro to fill a field with random data and then base64 it. */ -#define FILL_WITH_FAKE_DATA_AND_BASE64(field) STMT_BEGIN \ - crypto_rand((char *)field, sizeof(field)); \ - retval = base64_encode_nopad(field##_b64, sizeof(field##_b64), \ - field, sizeof(field)); \ - tor_assert(retval > 0); \ + char client_id_b64[HS_DESC_CLIENT_ID_LEN * 2]; + char iv_b64[CIPHER_IV_LEN * 2]; + char encrypted_cookie_b64[HS_DESC_ENCRYPED_COOKIE_LEN * 2]; + +#define ASSERT_AND_BASE64(field) STMT_BEGIN \ + tor_assert(!tor_mem_is_zero((char *) client->field, \ + sizeof(client->field))); \ + ret = base64_encode_nopad(field##_b64, sizeof(field##_b64), \ + client->field, sizeof(client->field)); \ + tor_assert(ret > 0); \ STMT_END - { /* Get those fakes! */ - uint8_t client_id[8]; /* fake client-id */ - uint8_t iv[16]; /* fake IV (initialization vector) */ - uint8_t encrypted_cookie[16]; /* fake encrypted cookie */ - - FILL_WITH_FAKE_DATA_AND_BASE64(client_id); - FILL_WITH_FAKE_DATA_AND_BASE64(iv); - FILL_WITH_FAKE_DATA_AND_BASE64(encrypted_cookie); - } + ASSERT_AND_BASE64(client_id); + ASSERT_AND_BASE64(iv); + ASSERT_AND_BASE64(encrypted_cookie); /* Build the final string */ tor_asprintf(&auth_client_str, "%s %s %s %s", str_desc_auth_client, client_id_b64, iv_b64, encrypted_cookie_b64); -#undef FILL_WITH_FAKE_DATA_AND_BASE64 +#undef ASSERT_AND_BASE64 return auth_client_str; } -/** How many lines of "client-auth" we want in our descriptors; fake or not. */ -#define CLIENT_AUTH_ENTRIES_BLOCK_SIZE 16 - /** Create the "client-auth" part of the descriptor and return a * newly-allocated string with it. It's the responsibility of the caller to * free the returned string. */ static char * -get_fake_auth_client_lines(void) +get_all_auth_client_lines(const hs_descriptor_t *desc) { - /* XXX: Client authorization is still not implemented, so all this function - does is make fake clients */ - int i = 0; smartlist_t *auth_client_lines = smartlist_new(); char *auth_client_lines_str = NULL; - /* Make a line for each fake client */ - const int num_fake_clients = CLIENT_AUTH_ENTRIES_BLOCK_SIZE; - for (i = 0; i < num_fake_clients; i++) { - char *auth_client_str = get_fake_auth_client_str(); - tor_assert(auth_client_str); + tor_assert(desc); + tor_assert(desc->superencrypted_data.clients); + tor_assert(smartlist_len(desc->superencrypted_data.clients) != 0); + tor_assert(smartlist_len(desc->superencrypted_data.clients) + % HS_DESC_AUTH_CLIENT_MULTIPLE == 0); + + /* Make a line for each client */ + SMARTLIST_FOREACH_BEGIN(desc->superencrypted_data.clients, + const hs_desc_authorized_client_t *, client) { + char *auth_client_str = NULL; + + auth_client_str = get_auth_client_str(client); + smartlist_add(auth_client_lines, auth_client_str); - } + } SMARTLIST_FOREACH_END(client); /* Join all lines together to form final string */ auth_client_lines_str = smartlist_join_strings(auth_client_lines, @@ -817,32 +807,29 @@ get_outer_encrypted_layer_plaintext(const hs_descriptor_t *desc, char *layer1_str = NULL; smartlist_t *lines = smartlist_new(); - /* XXX: Disclaimer: This function generates only _fake_ client auth - * data. Real client auth is not yet implemented, but client auth data MUST - * always be present in descriptors. In the future this function will be - * refactored to use real client auth data if they exist (#20700). */ - (void) *desc; - /* Specify auth type */ smartlist_add_asprintf(lines, "%s %s\n", str_desc_auth_type, "x25519"); - { /* Create fake ephemeral x25519 key */ - char fake_key_base64[CURVE25519_BASE64_PADDED_LEN + 1]; - curve25519_keypair_t fake_x25519_keypair; - if (curve25519_keypair_generate(&fake_x25519_keypair, 0) < 0) { - goto done; - } - if (curve25519_public_to_base64(fake_key_base64, - &fake_x25519_keypair.pubkey) < 0) { + { /* Print ephemeral x25519 key */ + char ephemeral_key_base64[CURVE25519_BASE64_PADDED_LEN + 1]; + const curve25519_public_key_t *ephemeral_pubkey; + + ephemeral_pubkey = &desc->superencrypted_data.auth_ephemeral_pubkey; + tor_assert(!tor_mem_is_zero((char *) ephemeral_pubkey->public_key, + CURVE25519_PUBKEY_LEN)); + + if (curve25519_public_to_base64(ephemeral_key_base64, + ephemeral_pubkey) < 0) { goto done; } smartlist_add_asprintf(lines, "%s %s\n", - str_desc_auth_key, fake_key_base64); - /* No need to memwipe any of these fake keys. They will go unused. */ + str_desc_auth_key, ephemeral_key_base64); + + memwipe(ephemeral_key_base64, 0, sizeof(ephemeral_key_base64)); } - { /* Create fake auth-client lines. */ - char *auth_client_lines = get_fake_auth_client_lines(); + { /* Create auth-client lines. */ + char *auth_client_lines = get_all_auth_client_lines(desc); tor_assert(auth_client_lines); smartlist_add(lines, auth_client_lines); } @@ -860,6 +847,8 @@ get_outer_encrypted_layer_plaintext(const hs_descriptor_t *desc, layer1_str = smartlist_join_strings(lines, "", 0, NULL); done: + /* We need to memwipe all lines because it contains the ephemeral key */ + SMARTLIST_FOREACH(lines, char *, a, memwipe(a, 0, strlen(a))); SMARTLIST_FOREACH(lines, char *, a, tor_free(a)); smartlist_free(lines); @@ -868,11 +857,14 @@ get_outer_encrypted_layer_plaintext(const hs_descriptor_t *desc, /* Encrypt <b>encoded_str</b> into an encrypted blob and then base64 it before * returning it. <b>desc</b> is provided to derive the encryption - * keys. <b>is_superencrypted_layer</b> is set if <b>encoded_str</b> is the + * keys. <b>secret_data</b> is also proved to derive the encryption keys. + * <b>is_superencrypted_layer</b> is set if <b>encoded_str</b> is the * middle (superencrypted) layer of the descriptor. It's the responsibility of * the caller to free the returned string. */ static char * encrypt_desc_data_and_base64(const hs_descriptor_t *desc, + const uint8_t *secret_data, + size_t secret_data_len, const char *encoded_str, int is_superencrypted_layer) { @@ -880,7 +872,8 @@ encrypt_desc_data_and_base64(const hs_descriptor_t *desc, ssize_t enc_b64_len, ret_len, enc_len; char *encrypted_blob = NULL; - enc_len = encrypt_descriptor_data(desc, encoded_str, &encrypted_blob, + enc_len = encrypt_descriptor_data(desc, secret_data, secret_data_len, + encoded_str, &encrypted_blob, is_superencrypted_layer); /* Get the encoded size plus a NUL terminating byte. */ enc_b64_len = base64_encode_size(enc_len, BASE64_ENCODE_MULTILINE) + 1; @@ -895,6 +888,53 @@ encrypt_desc_data_and_base64(const hs_descriptor_t *desc, return enc_b64; } +/* Generate the secret data which is used to encrypt/decrypt the descriptor. + * + * SECRET_DATA = blinded-public-key + * SECRET_DATA = blinded-public-key | descriptor_cookie + * + * The descriptor_cookie is optional but if it exists, it must be at least + * HS_DESC_DESCRIPTOR_COOKIE_LEN bytes long. + * + * A newly allocated secret data is put in secret_data_out. Return the + * length of the secret data. This function cannot fail. */ +static size_t +build_secret_data(const ed25519_public_key_t *blinded_pubkey, + const uint8_t *descriptor_cookie, + uint8_t **secret_data_out) +{ + size_t secret_data_len; + uint8_t *secret_data; + + tor_assert(blinded_pubkey); + tor_assert(secret_data_out); + + if (descriptor_cookie) { + /* If the descriptor cookie is present, we need both the blinded + * pubkey and the descriptor cookie as a secret data. */ + secret_data_len = ED25519_PUBKEY_LEN + HS_DESC_DESCRIPTOR_COOKIE_LEN; + secret_data = tor_malloc(secret_data_len); + + memcpy(secret_data, + blinded_pubkey->pubkey, + ED25519_PUBKEY_LEN); + memcpy(secret_data + ED25519_PUBKEY_LEN, + descriptor_cookie, + HS_DESC_DESCRIPTOR_COOKIE_LEN); + } else { + /* If the descriptor cookie is not present, we need only the blinded + * pubkey as a secret data. */ + secret_data_len = ED25519_PUBKEY_LEN; + secret_data = tor_malloc(secret_data_len); + memcpy(secret_data, + blinded_pubkey->pubkey, + ED25519_PUBKEY_LEN); + } + + *secret_data_out = secret_data; + return secret_data_len; +} + /* Generate and encode the superencrypted portion of <b>desc</b>. This also * involves generating the encrypted portion of the descriptor, and performing * the superencryption. A newly allocated NUL-terminated string pointer @@ -902,9 +942,12 @@ encrypt_desc_data_and_base64(const hs_descriptor_t *desc, * on success else a negative value. */ static int encode_superencrypted_data(const hs_descriptor_t *desc, + const uint8_t *descriptor_cookie, char **encrypted_blob_out) { int ret = -1; + uint8_t *secret_data = NULL; + size_t secret_data_len = 0; char *layer2_str = NULL; char *layer2_b64_ciphertext = NULL; char *layer1_str = NULL; @@ -924,8 +967,14 @@ encode_superencrypted_data(const hs_descriptor_t *desc, goto err; } + secret_data_len = build_secret_data(&desc->plaintext_data.blinded_pubkey, + descriptor_cookie, + &secret_data); + /* Encrypt and b64 the inner layer */ - layer2_b64_ciphertext = encrypt_desc_data_and_base64(desc, layer2_str, 0); + layer2_b64_ciphertext = + encrypt_desc_data_and_base64(desc, secret_data, secret_data_len, + layer2_str, 0); if (!layer2_b64_ciphertext) { goto err; } @@ -937,7 +986,11 @@ encode_superencrypted_data(const hs_descriptor_t *desc, } /* Encrypt and base64 the middle layer */ - layer1_b64_ciphertext = encrypt_desc_data_and_base64(desc, layer1_str, 1); + layer1_b64_ciphertext = + encrypt_desc_data_and_base64(desc, + desc->plaintext_data.blinded_pubkey.pubkey, + ED25519_PUBKEY_LEN, + layer1_str, 1); if (!layer1_b64_ciphertext) { goto err; } @@ -946,6 +999,8 @@ encode_superencrypted_data(const hs_descriptor_t *desc, ret = 0; err: + memwipe(secret_data, 0, secret_data_len); + tor_free(secret_data); tor_free(layer1_str); tor_free(layer2_str); tor_free(layer2_b64_ciphertext); @@ -959,7 +1014,9 @@ encode_superencrypted_data(const hs_descriptor_t *desc, * and encoded_out is untouched. */ static int desc_encode_v3(const hs_descriptor_t *desc, - const ed25519_keypair_t *signing_kp, char **encoded_out) + const ed25519_keypair_t *signing_kp, + const uint8_t *descriptor_cookie, + char **encoded_out) { int ret = -1; char *encoded_str = NULL; @@ -1007,7 +1064,8 @@ desc_encode_v3(const hs_descriptor_t *desc, /* Build the superencrypted data section. */ { char *enc_b64_blob=NULL; - if (encode_superencrypted_data(desc, &enc_b64_blob) < 0) { + if (encode_superencrypted_data(desc, descriptor_cookie, + &enc_b64_blob) < 0) { goto err; } smartlist_add_asprintf(lines, @@ -1067,6 +1125,42 @@ desc_encode_v3(const hs_descriptor_t *desc, /* === DECODING === */ +/* Given the token tok for an auth client, decode it as + * hs_desc_authorized_client_t. tok->args MUST contain at least 3 elements + * Return 0 on success else -1 on failure. */ +static int +decode_auth_client(const directory_token_t *tok, + hs_desc_authorized_client_t *client) +{ + int ret = -1; + + tor_assert(tok); + tor_assert(tok->n_args >= 3); + tor_assert(client); + + if (base64_decode((char *) client->client_id, sizeof(client->client_id), + tok->args[0], strlen(tok->args[0])) != + sizeof(client->client_id)) { + goto done; + } + if (base64_decode((char *) client->iv, sizeof(client->iv), + tok->args[1], strlen(tok->args[1])) != + sizeof(client->iv)) { + goto done; + } + if (base64_decode((char *) client->encrypted_cookie, + sizeof(client->encrypted_cookie), + tok->args[2], strlen(tok->args[2])) != + sizeof(client->encrypted_cookie)) { + goto done; + } + + /* Success. */ + ret = 0; + done: + return ret; +} + /* Given an encoded string of the link specifiers, return a newly allocated * list of decoded link specifiers. Return NULL on error. */ STATIC smartlist_t * @@ -1306,11 +1400,81 @@ encrypted_data_length_is_valid(size_t len) return 0; } +/* Decrypt the descriptor cookie given the descriptor, the auth client, + * and the client secret key. On sucess, return 0 and a newly allocated + * descriptor cookie descriptor_cookie_out. On error or if the client id + * is invalid, return -1 and descriptor_cookie_out is set to + * NULL. */ +static int +decrypt_descriptor_cookie(const hs_descriptor_t *desc, + const hs_desc_authorized_client_t *client, + const curve25519_secret_key_t *client_auth_sk, + uint8_t **descriptor_cookie_out) +{ + int ret = -1; + uint8_t secret_seed[CURVE25519_OUTPUT_LEN]; + uint8_t keystream[HS_DESC_CLIENT_ID_LEN + HS_DESC_COOKIE_KEY_LEN]; + uint8_t *cookie_key = NULL; + uint8_t *descriptor_cookie = NULL; + crypto_cipher_t *cipher = NULL; + crypto_xof_t *xof = NULL; + + tor_assert(desc); + tor_assert(client); + tor_assert(client_auth_sk); + tor_assert(!tor_mem_is_zero( + (char *) &desc->superencrypted_data.auth_ephemeral_pubkey, + sizeof(desc->superencrypted_data.auth_ephemeral_pubkey))); + tor_assert(!tor_mem_is_zero((char *) client_auth_sk, + sizeof(*client_auth_sk))); + tor_assert(!tor_mem_is_zero((char *) desc->subcredential, DIGEST256_LEN)); + + /* Calculate x25519(client_x, hs_Y) */ + curve25519_handshake(secret_seed, client_auth_sk, + &desc->superencrypted_data.auth_ephemeral_pubkey); + + /* Calculate KEYS = KDF(subcredential | SECRET_SEED, 40) */ + xof = crypto_xof_new(); + crypto_xof_add_bytes(xof, desc->subcredential, DIGEST256_LEN); + crypto_xof_add_bytes(xof, secret_seed, sizeof(secret_seed)); + crypto_xof_squeeze_bytes(xof, keystream, sizeof(keystream)); + crypto_xof_free(xof); + + /* If the client id of auth client is not the same as the calculcated + * client id, it means that this auth client is invaild according to the + * client secret key client_auth_sk. */ + if (tor_memneq(client->client_id, keystream, HS_DESC_CLIENT_ID_LEN)) { + goto done; + } + cookie_key = keystream + HS_DESC_CLIENT_ID_LEN; + + /* This creates a cipher for AES. It can't fail. */ + cipher = crypto_cipher_new_with_iv_and_bits(cookie_key, client->iv, + HS_DESC_COOKIE_KEY_BIT_SIZE); + descriptor_cookie = tor_malloc_zero(HS_DESC_DESCRIPTOR_COOKIE_LEN); + /* This can't fail. */ + crypto_cipher_decrypt(cipher, (char *) descriptor_cookie, + (const char *) client->encrypted_cookie, + sizeof(client->encrypted_cookie)); + + /* Success. */ + ret = 0; + done: + *descriptor_cookie_out = descriptor_cookie; + if (cipher) { + crypto_cipher_free(cipher); + } + memwipe(secret_seed, 0, sizeof(secret_seed)); + memwipe(keystream, 0, sizeof(keystream)); + return ret; +} + /** Decrypt an encrypted descriptor layer at <b>encrypted_blob</b> of size - * <b>encrypted_blob_size</b>. Use the descriptor object <b>desc</b> to - * generate the right decryption keys; set <b>decrypted_out</b> to the - * plaintext. If <b>is_superencrypted_layer</b> is set, this is the outter - * encrypted layer of the descriptor. + * <b>encrypted_blob_size</b>. The descriptor cookie is optional. Use + * the descriptor object <b>desc</b> and <b>descriptor_cookie</b> + * to generate the right decryption keys; set <b>decrypted_out</b> to + * the plaintext. If <b>is_superencrypted_layer</b> is set, this is + * the outter encrypted layer of the descriptor. * * On any error case, including an empty output, return 0 and set * *<b>decrypted_out</b> to NULL. @@ -1319,11 +1483,14 @@ MOCK_IMPL(STATIC size_t, decrypt_desc_layer,(const hs_descriptor_t *desc, const uint8_t *encrypted_blob, size_t encrypted_blob_size, + const uint8_t *descriptor_cookie, int is_superencrypted_layer, char **decrypted_out)) { uint8_t *decrypted = NULL; uint8_t secret_key[HS_DESC_ENCRYPTED_KEY_LEN], secret_iv[CIPHER_IV_LEN]; + uint8_t *secret_data = NULL; + size_t secret_data_len = 0; uint8_t mac_key[DIGEST256_LEN], our_mac[DIGEST256_LEN]; const uint8_t *salt, *encrypted, *desc_mac; size_t encrypted_len, result_len = 0; @@ -1350,9 +1517,15 @@ decrypt_desc_layer,(const hs_descriptor_t *desc, /* And last comes the MAC. */ desc_mac = encrypted_blob + encrypted_blob_size - DIGEST256_LEN; + /* Build secret data to be used in the decryption. */ + secret_data_len = build_secret_data(&desc->plaintext_data.blinded_pubkey, + descriptor_cookie, + &secret_data); + /* KDF construction resulting in a key from which the secret key, IV and MAC * key are extracted which is what we need for the decryption. */ - build_secret_key_iv_mac(desc, salt, HS_DESC_ENCRYPTED_SALT_LEN, + build_secret_key_iv_mac(desc, secret_data, secret_data_len, + salt, HS_DESC_ENCRYPTED_SALT_LEN, secret_key, sizeof(secret_key), secret_iv, sizeof(secret_iv), mac_key, sizeof(mac_key), @@ -1412,167 +1585,98 @@ decrypt_desc_layer,(const hs_descriptor_t *desc, result_len = 0; done: + memwipe(secret_data, 0, secret_data_len); memwipe(secret_key, 0, sizeof(secret_key)); memwipe(secret_iv, 0, sizeof(secret_iv)); + tor_free(secret_data); return result_len; } -/* Basic validation that the superencrypted client auth portion of the - * descriptor is well-formed and recognized. Return True if so, otherwise - * return False. */ -static int -superencrypted_auth_data_is_valid(smartlist_t *tokens) -{ - /* XXX: This is just basic validation for now. When we implement client auth, - we can refactor this function so that it actually parses and saves the - data. */ - - { /* verify desc auth type */ - const directory_token_t *tok; - tok = find_by_keyword(tokens, R3_DESC_AUTH_TYPE); - tor_assert(tok->n_args >= 1); - if (strcmp(tok->args[0], "x25519")) { - log_warn(LD_DIR, "Unrecognized desc auth type"); - return 0; - } - } - - { /* verify desc auth key */ - const directory_token_t *tok; - curve25519_public_key_t k; - tok = find_by_keyword(tokens, R3_DESC_AUTH_KEY); - tor_assert(tok->n_args >= 1); - if (curve25519_public_from_base64(&k, tok->args[0]) < 0) { - log_warn(LD_DIR, "Bogus desc auth key in HS desc"); - return 0; - } - } - - /* verify desc auth client items */ - SMARTLIST_FOREACH_BEGIN(tokens, const directory_token_t *, tok) { - if (tok->tp == R3_DESC_AUTH_CLIENT) { - tor_assert(tok->n_args >= 3); - } - } SMARTLIST_FOREACH_END(tok); - - return 1; -} - -/* Parse <b>message</b>, the plaintext of the superencrypted portion of an HS - * descriptor. Set <b>encrypted_out</b> to the encrypted blob, and return its - * size */ -STATIC size_t -decode_superencrypted(const char *message, size_t message_len, - uint8_t **encrypted_out) -{ - int retval = 0; - memarea_t *area = NULL; - smartlist_t *tokens = NULL; - - area = memarea_new(); - tokens = smartlist_new(); - if (tokenize_string(area, message, message + message_len, tokens, - hs_desc_superencrypted_v3_token_table, 0) < 0) { - log_warn(LD_REND, "Superencrypted portion is not parseable"); - goto err; - } - - /* Do some rudimentary validation of the authentication data */ - if (!superencrypted_auth_data_is_valid(tokens)) { - log_warn(LD_REND, "Invalid auth data"); - goto err; - } - - /* Extract the encrypted data section. */ - { - const directory_token_t *tok; - tok = find_by_keyword(tokens, R3_ENCRYPTED); - tor_assert(tok->object_body); - if (strcmp(tok->object_type, "MESSAGE") != 0) { - log_warn(LD_REND, "Desc superencrypted data section is invalid"); - goto err; - } - /* Make sure the length of the encrypted blob is valid. */ - if (!encrypted_data_length_is_valid(tok->object_size)) { - goto err; - } - - /* Copy the encrypted blob to the descriptor object so we can handle it - * latter if needed. */ - tor_assert(tok->object_size <= INT_MAX); - *encrypted_out = tor_memdup(tok->object_body, tok->object_size); - retval = (int) tok->object_size; - } - - err: - SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); - smartlist_free(tokens); - if (area) { - memarea_drop_all(area); - } - - return retval; -} - -/* Decrypt both the superencrypted and the encrypted section of the descriptor - * using the given descriptor object <b>desc</b>. A newly allocated NUL - * terminated string is put in decrypted_out which contains the inner encrypted - * layer of the descriptor. Return the length of decrypted_out on success else - * 0 is returned and decrypted_out is set to NULL. */ +/* Decrypt the superencrypted section of the descriptor using the given + * descriptor object <b>desc</b>. A newly allocated NUL terminated string is + * put in decrypted_out which contains the superencrypted layer of the + * descriptor. Return the length of decrypted_out on success else 0 is + * returned and decrypted_out is set to NULL. */ static size_t -desc_decrypt_all(const hs_descriptor_t *desc, char **decrypted_out) +desc_decrypt_superencrypted(const hs_descriptor_t *desc, char **decrypted_out) { - size_t decrypted_len = 0; - size_t encrypted_len = 0; size_t superencrypted_len = 0; char *superencrypted_plaintext = NULL; - uint8_t *encrypted_blob = NULL; - /** Function logic: This function takes us from the descriptor header to the - * inner encrypted layer, by decrypting and decoding the middle descriptor - * layer. In the end we return the contents of the inner encrypted layer to - * our caller. */ + tor_assert(desc); + tor_assert(decrypted_out); - /* 1. Decrypt middle layer of descriptor */ superencrypted_len = decrypt_desc_layer(desc, desc->plaintext_data.superencrypted_blob, desc->plaintext_data.superencrypted_blob_size, - 1, - &superencrypted_plaintext); + NULL, 1, &superencrypted_plaintext); + if (!superencrypted_len) { log_warn(LD_REND, "Decrypting superencrypted desc failed."); - goto err; + goto done; } tor_assert(superencrypted_plaintext); - /* 2. Parse "superencrypted" */ - encrypted_len = decode_superencrypted(superencrypted_plaintext, - superencrypted_len, - &encrypted_blob); - if (!encrypted_len) { - log_warn(LD_REND, "Decrypting encrypted desc failed."); - goto err; + done: + /* In case of error, superencrypted_plaintext is already NULL, so the + * following line makes sense. */ + *decrypted_out = superencrypted_plaintext; + /* This makes sense too, because, in case of error, this is zero. */ + return superencrypted_len; +} + +/* Decrypt the encrypted section of the descriptor using the given descriptor + * object <b>desc</b>. A newly allocated NUL terminated string is put in + * decrypted_out which contains the encrypted layer of the descriptor. + * Return the length of decrypted_out on success else 0 is returned and + * decrypted_out is set to NULL. */ +static size_t +desc_decrypt_encrypted(const hs_descriptor_t *desc, + const curve25519_secret_key_t *client_auth_sk, + char **decrypted_out) +{ + size_t encrypted_len = 0; + char *encrypted_plaintext = NULL; + uint8_t *descriptor_cookie = NULL; + + tor_assert(desc); + tor_assert(desc->superencrypted_data.clients); + tor_assert(decrypted_out); + + /* If the client secret key is provided, try to find a valid descriptor + * cookie. Otherwise, leave it NULL. */ + if (client_auth_sk) { + SMARTLIST_FOREACH_BEGIN(desc->superencrypted_data.clients, + hs_desc_authorized_client_t *, client) { + /* If we can decrypt the descriptor cookie successfully, we will use that + * descriptor cookie and break from the loop. */ + if (!decrypt_descriptor_cookie(desc, client, client_auth_sk, + &descriptor_cookie)) { + break; + } + } SMARTLIST_FOREACH_END(client); } - tor_assert(encrypted_blob); - /* 3. Decrypt "encrypted" and set decrypted_out */ - char *decrypted_desc; - decrypted_len = decrypt_desc_layer(desc, - encrypted_blob, encrypted_len, - 0, &decrypted_desc); - if (!decrypted_len) { + encrypted_len = decrypt_desc_layer(desc, + desc->superencrypted_data.encrypted_blob, + desc->superencrypted_data.encrypted_blob_size, + descriptor_cookie, 0, &encrypted_plaintext); + if (!encrypted_len) { log_warn(LD_REND, "Decrypting encrypted desc failed."); goto err; } - tor_assert(decrypted_desc); - - *decrypted_out = decrypted_desc; + tor_assert(encrypted_plaintext); err: - tor_free(superencrypted_plaintext); - tor_free(encrypted_blob); - - return decrypted_len; + /* In case of error, encrypted_plaintext is already NULL, so the + * following line makes sense. */ + *decrypted_out = encrypted_plaintext; + if (descriptor_cookie) { + memwipe(descriptor_cookie, 0, HS_DESC_DESCRIPTOR_COOKIE_LEN); + } + tor_free(descriptor_cookie); + /* This makes sense too, because, in case of error, this is zero. */ + return encrypted_len; } /* Given the token tok for an intro point legacy key, the list of tokens, the @@ -1999,19 +2103,19 @@ desc_decode_plaintext_v3(smartlist_t *tokens, goto err; } - /* Extract the encrypted data section. */ + /* Extract the superencrypted data section. */ tok = find_by_keyword(tokens, R3_SUPERENCRYPTED); tor_assert(tok->object_body); if (strcmp(tok->object_type, "MESSAGE") != 0) { - log_warn(LD_REND, "Service descriptor encrypted data section is invalid"); + log_warn(LD_REND, "Desc superencrypted data section is invalid"); goto err; } - /* Make sure the length of the encrypted blob is valid. */ + /* Make sure the length of the superencrypted blob is valid. */ if (!encrypted_data_length_is_valid(tok->object_size)) { goto err; } - /* Copy the encrypted blob to the descriptor object so we can handle it + /* Copy the superencrypted blob to the descriptor object so we can handle it * latter if needed. */ desc->superencrypted_blob = tor_memdup(tok->object_body, tok->object_size); desc->superencrypted_blob_size = tok->object_size; @@ -2031,14 +2135,130 @@ desc_decode_plaintext_v3(smartlist_t *tokens, return -1; } +/* Decode the version 3 superencrypted section of the given descriptor desc. + * The desc_superencrypted_out will be populated with the decoded data. + * Return 0 on success else -1. */ +static int +desc_decode_superencrypted_v3(const hs_descriptor_t *desc, + hs_desc_superencrypted_data_t * + desc_superencrypted_out) +{ + int ret = -1; + char *message = NULL; + size_t message_len; + memarea_t *area = NULL; + directory_token_t *tok; + smartlist_t *tokens = NULL; + /* Rename the parameter because it is too long. */ + hs_desc_superencrypted_data_t *superencrypted = desc_superencrypted_out; + + tor_assert(desc); + tor_assert(desc_superencrypted_out); + + /* Decrypt the superencrypted data that is located in the plaintext section + * in the descriptor as a blob of bytes. */ + message_len = desc_decrypt_superencrypted(desc, &message); + if (!message_len) { + log_warn(LD_REND, "Service descriptor decryption failed."); + goto err; + } + tor_assert(message); + + area = memarea_new(); + tokens = smartlist_new(); + if (tokenize_string(area, message, message + message_len, + tokens, hs_desc_superencrypted_v3_token_table, 0) < 0) { + log_warn(LD_REND, "Superencrypted service descriptor is not parseable."); + goto err; + } + + /* Verify desc auth type */ + tok = find_by_keyword(tokens, R3_DESC_AUTH_TYPE); + tor_assert(tok->n_args >= 1); + if (strcmp(tok->args[0], "x25519")) { + log_warn(LD_DIR, "Unrecognized desc auth type"); + goto err; + } + + /* Extract desc auth ephemeral key */ + tok = find_by_keyword(tokens, R3_DESC_AUTH_KEY); + tor_assert(tok->n_args >= 1); + if (curve25519_public_from_base64(&superencrypted->auth_ephemeral_pubkey, + tok->args[0]) < 0) { + log_warn(LD_DIR, "Bogus desc auth ephemeral key in HS desc"); + goto err; + } + + /* Extract desc auth client items */ + if (!superencrypted->clients) { + superencrypted->clients = smartlist_new(); + } + SMARTLIST_FOREACH_BEGIN(tokens, const directory_token_t *, token) { + if (token->tp == R3_DESC_AUTH_CLIENT) { + tor_assert(token->n_args >= 3); + + hs_desc_authorized_client_t *client = + tor_malloc_zero(sizeof(hs_desc_authorized_client_t)); + + if (decode_auth_client(token, client) < 0) { + log_warn(LD_REND, "Descriptor client authorization section can't " + "be decoded."); + tor_free(client); + goto err; + } + smartlist_add(superencrypted->clients, client); + } + } SMARTLIST_FOREACH_END(token); + + /* Extract the encrypted data section. */ + tok = find_by_keyword(tokens, R3_ENCRYPTED); + tor_assert(tok->object_body); + if (strcmp(tok->object_type, "MESSAGE") != 0) { + log_warn(LD_REND, "Desc encrypted data section is invalid"); + goto err; + } + /* Make sure the length of the encrypted blob is valid. */ + if (!encrypted_data_length_is_valid(tok->object_size)) { + goto err; + } + + /* Copy the encrypted blob to the descriptor object so we can handle it + * latter if needed. */ + tor_assert(tok->object_size <= INT_MAX); + superencrypted->encrypted_blob = tor_memdup(tok->object_body, + tok->object_size); + superencrypted->encrypted_blob_size = tok->object_size; + + ret = 0; + goto done; + + err: + tor_assert(ret < 0); + hs_desc_superencrypted_data_free_contents(desc_superencrypted_out); + + done: + if (tokens) { + SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); + smartlist_free(tokens); + } + if (area) { + memarea_drop_all(area); + } + if (message) { + tor_free(message); + } + return ret; +} + /* Decode the version 3 encrypted section of the given descriptor desc. The * desc_encrypted_out will be populated with the decoded data. Return 0 on * success else -1. */ static int desc_decode_encrypted_v3(const hs_descriptor_t *desc, + const curve25519_secret_key_t *client_auth_sk, hs_desc_encrypted_data_t *desc_encrypted_out) { - int result = -1; + int ret = -1; char *message = NULL; size_t message_len; memarea_t *area = NULL; @@ -2048,9 +2268,9 @@ desc_decode_encrypted_v3(const hs_descriptor_t *desc, tor_assert(desc); tor_assert(desc_encrypted_out); - /* Decrypt the superencrypted data that is located in the plaintext section + /* Decrypt the encrypted data that is located in the superencrypted section * in the descriptor as a blob of bytes. */ - message_len = desc_decrypt_all(desc, &message); + message_len = desc_decrypt_encrypted(desc, client_auth_sk, &message); if (!message_len) { log_warn(LD_REND, "Service descriptor decryption failed."); goto err; @@ -2109,12 +2329,12 @@ desc_decode_encrypted_v3(const hs_descriptor_t *desc, /* NOTE: Unknown fields are allowed because this function could be used to * decode other descriptor version. */ - result = 0; + ret = 0; goto done; err: - tor_assert(result < 0); - desc_encrypted_data_free_contents(desc_encrypted_out); + tor_assert(ret < 0); + hs_desc_encrypted_data_free_contents(desc_encrypted_out); done: if (tokens) { @@ -2127,7 +2347,7 @@ desc_decode_encrypted_v3(const hs_descriptor_t *desc, if (message) { tor_free(message); } - return result; + return ret; } /* Table of encrypted decode function version specific. The function are @@ -2135,6 +2355,7 @@ desc_decode_encrypted_v3(const hs_descriptor_t *desc, static int (*decode_encrypted_handlers[])( const hs_descriptor_t *desc, + const curve25519_secret_key_t *client_auth_sk, hs_desc_encrypted_data_t *desc_encrypted) = { /* v0 */ NULL, /* v1 */ NULL, /* v2 */ NULL, @@ -2146,6 +2367,7 @@ static int * negative value on error. */ int hs_desc_decode_encrypted(const hs_descriptor_t *desc, + const curve25519_secret_key_t *client_auth_sk, hs_desc_encrypted_data_t *desc_encrypted) { int ret; @@ -2156,9 +2378,9 @@ hs_desc_decode_encrypted(const hs_descriptor_t *desc, version = desc->plaintext_data.version; tor_assert(desc_encrypted); /* Calling this function without an encrypted blob to parse is a code flow - * error. The plaintext parsing should never succeed in the first place + * error. The superencrypted parsing should never succeed in the first place * without an encrypted section. */ - tor_assert(desc->plaintext_data.superencrypted_blob); + tor_assert(desc->superencrypted_data.encrypted_blob); /* Let's make sure we have a supported version as well. By correctly parsing * the plaintext, this should not fail. */ if (BUG(!hs_desc_is_supported_version(version))) { @@ -2171,7 +2393,59 @@ hs_desc_decode_encrypted(const hs_descriptor_t *desc, tor_assert(decode_encrypted_handlers[version]); /* Run the version specific plaintext decoder. */ - ret = decode_encrypted_handlers[version](desc, desc_encrypted); + ret = decode_encrypted_handlers[version](desc, client_auth_sk, + desc_encrypted); + if (ret < 0) { + goto err; + } + + err: + return ret; +} + +/* Table of superencrypted decode function version specific. The function are + * indexed by the version number so v3 callback is at index 3 in the array. */ +static int + (*decode_superencrypted_handlers[])( + const hs_descriptor_t *desc, + hs_desc_superencrypted_data_t *desc_superencrypted) = +{ + /* v0 */ NULL, /* v1 */ NULL, /* v2 */ NULL, + desc_decode_superencrypted_v3, +}; + +/* Decode the superencrypted data section of the given descriptor and store the + * data in the given superencrypted data object. Return 0 on success else a + * negative value on error. */ +int +hs_desc_decode_superencrypted(const hs_descriptor_t *desc, + hs_desc_superencrypted_data_t * + desc_superencrypted) +{ + int ret; + uint32_t version; + + tor_assert(desc); + /* Ease our life a bit. */ + version = desc->plaintext_data.version; + tor_assert(desc_superencrypted); + /* Calling this function without an superencrypted blob to parse is + * a code flow error. The plaintext parsing should never succeed in + * the first place without an superencrypted section. */ + tor_assert(desc->plaintext_data.superencrypted_blob); + /* Let's make sure we have a supported version as well. By correctly parsing + * the plaintext, this should not fail. */ + if (BUG(!hs_desc_is_supported_version(version))) { + ret = -1; + goto err; + } + /* Extra precaution. Having no handler for the supported version should + * never happened else we forgot to add it but we bumped the version. */ + tor_assert(ARRAY_LENGTH(decode_superencrypted_handlers) >= version); + tor_assert(decode_superencrypted_handlers[version]); + + /* Run the version specific plaintext decoder. */ + ret = decode_superencrypted_handlers[version](desc, desc_superencrypted); if (ret < 0) { goto err; } @@ -2267,13 +2541,15 @@ hs_desc_decode_plaintext(const char *encoded, } /* Fully decode an encoded descriptor and set a newly allocated descriptor - * object in desc_out. Subcredentials are used if not NULL else it's ignored. + * object in desc_out. Client secret key is used to decrypt the "encrypted" + * section if not NULL else it's ignored. * * Return 0 on success. A negative value is returned on error and desc_out is * set to NULL. */ int hs_desc_decode_descriptor(const char *encoded, const uint8_t *subcredential, + const curve25519_secret_key_t *client_auth_sk, hs_descriptor_t **desc_out) { int ret = -1; @@ -2283,8 +2559,9 @@ hs_desc_decode_descriptor(const char *encoded, desc = tor_malloc_zero(sizeof(hs_descriptor_t)); - /* Subcredentials are optional. */ - if (BUG(!subcredential)) { + /* Subcredentials are not optional. */ + if (BUG(!subcredential || + tor_mem_is_zero((char*)subcredential, DIGEST256_LEN))) { log_warn(LD_GENERAL, "Tried to decrypt without subcred. Impossible!"); goto err; } @@ -2296,7 +2573,12 @@ hs_desc_decode_descriptor(const char *encoded, goto err; } - ret = hs_desc_decode_encrypted(desc, &desc->encrypted_data); + ret = hs_desc_decode_superencrypted(desc, &desc->superencrypted_data); + if (ret < 0) { + goto err; + } + + ret = hs_desc_decode_encrypted(desc, client_auth_sk, &desc->encrypted_data); if (ret < 0) { goto err; } @@ -2324,6 +2606,7 @@ static int (*encode_handlers[])( const hs_descriptor_t *desc, const ed25519_keypair_t *signing_kp, + const uint8_t *descriptor_cookie, char **encoded_out) = { /* v0 */ NULL, /* v1 */ NULL, /* v2 */ NULL, @@ -2331,14 +2614,20 @@ static int }; /* Encode the given descriptor desc including signing with the given key pair - * signing_kp. On success, encoded_out points to a newly allocated NUL - * terminated string that contains the encoded descriptor as a string. + * signing_kp and encrypting with the given descriptor cookie. + * + * If the client authorization is enabled, descriptor_cookie must be the same + * as the one used to build hs_desc_authorized_client_t in the descriptor. + * Otherwise, it must be NULL. On success, encoded_out points to a newly + * allocated NUL terminated string that contains the encoded descriptor as + * a string. * * Return 0 on success and encoded_out is a valid pointer. On error, -1 is * returned and encoded_out is set to NULL. */ MOCK_IMPL(int, hs_desc_encode_descriptor,(const hs_descriptor_t *desc, const ed25519_keypair_t *signing_kp, + const uint8_t *descriptor_cookie, char **encoded_out)) { int ret = -1; @@ -2357,15 +2646,21 @@ hs_desc_encode_descriptor,(const hs_descriptor_t *desc, tor_assert(ARRAY_LENGTH(encode_handlers) >= version); tor_assert(encode_handlers[version]); - ret = encode_handlers[version](desc, signing_kp, encoded_out); + ret = encode_handlers[version](desc, signing_kp, + descriptor_cookie, encoded_out); if (ret < 0) { goto err; } - /* Try to decode what we just encoded. Symmetry is nice! */ - ret = hs_desc_decode_descriptor(*encoded_out, desc->subcredential, NULL); - if (BUG(ret < 0)) { - goto err; + /* Try to decode what we just encoded. Symmetry is nice!, but it is + * symmetric only if the client auth is disabled. That is, the descriptor + * cookie will be NULL. */ + if (!descriptor_cookie) { + ret = hs_desc_decode_descriptor(*encoded_out, desc->subcredential, + NULL, NULL); + if (BUG(ret < 0)) { + goto err; + } } return 0; @@ -2375,11 +2670,75 @@ hs_desc_encode_descriptor,(const hs_descriptor_t *desc, return ret; } +/* Free the content of the plaintext section of a descriptor. */ +void +hs_desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc) +{ + if (!desc) { + return; + } + + if (desc->superencrypted_blob) { + tor_free(desc->superencrypted_blob); + } + tor_cert_free(desc->signing_key_cert); + + memwipe(desc, 0, sizeof(*desc)); +} + +/* Free the content of the superencrypted section of a descriptor. */ +void +hs_desc_superencrypted_data_free_contents(hs_desc_superencrypted_data_t *desc) +{ + if (!desc) { + return; + } + + if (desc->encrypted_blob) { + tor_free(desc->encrypted_blob); + } + if (desc->clients) { + SMARTLIST_FOREACH(desc->clients, hs_desc_authorized_client_t *, client, + hs_desc_authorized_client_free(client)); + smartlist_free(desc->clients); + } + + memwipe(desc, 0, sizeof(*desc)); +} + +/* Free the content of the encrypted section of a descriptor. */ +void +hs_desc_encrypted_data_free_contents(hs_desc_encrypted_data_t *desc) +{ + if (!desc) { + return; + } + + if (desc->intro_auth_types) { + SMARTLIST_FOREACH(desc->intro_auth_types, char *, a, tor_free(a)); + smartlist_free(desc->intro_auth_types); + } + if (desc->intro_points) { + SMARTLIST_FOREACH(desc->intro_points, hs_desc_intro_point_t *, ip, + hs_desc_intro_point_free(ip)); + smartlist_free(desc->intro_points); + } + memwipe(desc, 0, sizeof(*desc)); +} + /* Free the descriptor plaintext data object. */ void hs_desc_plaintext_data_free_(hs_desc_plaintext_data_t *desc) { - desc_plaintext_data_free_contents(desc); + hs_desc_plaintext_data_free_contents(desc); + tor_free(desc); +} + +/* Free the descriptor plaintext data object. */ +void +hs_desc_superencrypted_data_free_(hs_desc_superencrypted_data_t *desc) +{ + hs_desc_superencrypted_data_free_contents(desc); tor_free(desc); } @@ -2387,7 +2746,7 @@ hs_desc_plaintext_data_free_(hs_desc_plaintext_data_t *desc) void hs_desc_encrypted_data_free_(hs_desc_encrypted_data_t *desc) { - desc_encrypted_data_free_contents(desc); + hs_desc_encrypted_data_free_contents(desc); tor_free(desc); } @@ -2399,8 +2758,9 @@ hs_descriptor_free_(hs_descriptor_t *desc) return; } - desc_plaintext_data_free_contents(&desc->plaintext_data); - desc_encrypted_data_free_contents(&desc->encrypted_data); + hs_desc_plaintext_data_free_contents(&desc->plaintext_data); + hs_desc_superencrypted_data_free_contents(&desc->superencrypted_data); + hs_desc_encrypted_data_free_contents(&desc->encrypted_data); tor_free(desc); } @@ -2475,6 +2835,94 @@ hs_desc_intro_point_free_(hs_desc_intro_point_t *ip) tor_free(ip); } +/* Allocate and build a new fake client info for the descriptor. Return a + * newly allocated object. This can't fail. */ +hs_desc_authorized_client_t * +hs_desc_build_fake_authorized_client(void) +{ + hs_desc_authorized_client_t *client_auth = + tor_malloc_zero(sizeof(*client_auth)); + + crypto_rand((char *) client_auth->client_id, + sizeof(client_auth->client_id)); + crypto_rand((char *) client_auth->iv, + sizeof(client_auth->iv)); + crypto_rand((char *) client_auth->encrypted_cookie, + sizeof(client_auth->encrypted_cookie)); + + return client_auth; +} + +/* Using the service's subcredential, client public key, auth ephemeral secret + * key, and descriptor cookie, build the auth client so we can then encode the + * descriptor for publication. client_out must be already allocated. */ +void +hs_desc_build_authorized_client(const uint8_t *subcredential, + const curve25519_public_key_t *client_auth_pk, + const curve25519_secret_key_t * + auth_ephemeral_sk, + const uint8_t *descriptor_cookie, + hs_desc_authorized_client_t *client_out) +{ + uint8_t secret_seed[CURVE25519_OUTPUT_LEN]; + uint8_t keystream[HS_DESC_CLIENT_ID_LEN + HS_DESC_COOKIE_KEY_LEN]; + uint8_t *cookie_key; + crypto_cipher_t *cipher; + crypto_xof_t *xof; + + tor_assert(client_auth_pk); + tor_assert(auth_ephemeral_sk); + tor_assert(descriptor_cookie); + tor_assert(client_out); + tor_assert(subcredential); + tor_assert(!tor_mem_is_zero((char *) auth_ephemeral_sk, + sizeof(*auth_ephemeral_sk))); + tor_assert(!tor_mem_is_zero((char *) client_auth_pk, + sizeof(*client_auth_pk))); + tor_assert(!tor_mem_is_zero((char *) descriptor_cookie, + HS_DESC_DESCRIPTOR_COOKIE_LEN)); + tor_assert(!tor_mem_is_zero((char *) subcredential, + DIGEST256_LEN)); + + /* Calculate x25519(hs_y, client_X) */ + curve25519_handshake(secret_seed, + auth_ephemeral_sk, + client_auth_pk); + + /* Calculate KEYS = KDF(subcredential | SECRET_SEED, 40) */ + xof = crypto_xof_new(); + crypto_xof_add_bytes(xof, subcredential, DIGEST256_LEN); + crypto_xof_add_bytes(xof, secret_seed, sizeof(secret_seed)); + crypto_xof_squeeze_bytes(xof, keystream, sizeof(keystream)); + crypto_xof_free(xof); + + memcpy(client_out->client_id, keystream, HS_DESC_CLIENT_ID_LEN); + cookie_key = keystream + HS_DESC_CLIENT_ID_LEN; + + /* Random IV */ + crypto_strongest_rand(client_out->iv, sizeof(client_out->iv)); + + /* This creates a cipher for AES. It can't fail. */ + cipher = crypto_cipher_new_with_iv_and_bits(cookie_key, client_out->iv, + HS_DESC_COOKIE_KEY_BIT_SIZE); + /* This can't fail. */ + crypto_cipher_encrypt(cipher, (char *) client_out->encrypted_cookie, + (const char *) descriptor_cookie, + HS_DESC_DESCRIPTOR_COOKIE_LEN); + + memwipe(secret_seed, 0, sizeof(secret_seed)); + memwipe(keystream, 0, sizeof(keystream)); + + crypto_cipher_free(cipher); +} + +/* Free an authoriezd client object. */ +void +hs_desc_authorized_client_free_(hs_desc_authorized_client_t *client) +{ + tor_free(client); +} + /* Free the given descriptor link specifier. */ void hs_desc_link_specifier_free_(hs_desc_link_specifier_t *ls) diff --git a/src/feature/hs/hs_descriptor.h b/src/feature/hs/hs_descriptor.h index bfdf7559c6..adfb94deaa 100644 --- a/src/feature/hs/hs_descriptor.h +++ b/src/feature/hs/hs_descriptor.h @@ -37,12 +37,6 @@ struct link_specifier_t; #define HS_DESC_CERT_LIFETIME (54 * 60 * 60) /* Length of the salt needed for the encrypted section of a descriptor. */ #define HS_DESC_ENCRYPTED_SALT_LEN 16 -/* Length of the secret input needed for the KDF construction which derives - * the encryption key for the encrypted data section of the descriptor. This - * adds up to 68 bytes being the blinded key, hashed subcredential and - * revision counter. */ -#define HS_DESC_ENCRYPTED_SECRET_INPUT_LEN \ - ED25519_PUBKEY_LEN + DIGEST256_LEN + sizeof(uint64_t) /* Length of the KDF output value which is the length of the secret key, * the secret IV and MAC key length which is the length of H() output. */ #define HS_DESC_ENCRYPTED_KDF_OUTPUT_LEN \ @@ -59,6 +53,17 @@ struct link_specifier_t; #define HS_DESC_ENCRYPTED_KEY_LEN CIPHER256_KEY_LEN #define HS_DESC_ENCRYPTED_BIT_SIZE (HS_DESC_ENCRYPTED_KEY_LEN * 8) +/* Length of each components in the auth client section in the descriptor. */ +#define HS_DESC_CLIENT_ID_LEN 8 +#define HS_DESC_DESCRIPTOR_COOKIE_LEN 16 +#define HS_DESC_COOKIE_KEY_LEN 32 +#define HS_DESC_COOKIE_KEY_BIT_SIZE (HS_DESC_COOKIE_KEY_LEN * 8) +#define HS_DESC_ENCRYPED_COOKIE_LEN HS_DESC_DESCRIPTOR_COOKIE_LEN + +/* The number of auth client entries in the descriptor must be the multiple + * of this constant. */ +#define HS_DESC_AUTH_CLIENT_MULTIPLE 16 + /* Type of authentication in the descriptor. */ typedef enum { HS_DESC_AUTH_ED25519 = 1 @@ -126,6 +131,20 @@ typedef struct hs_desc_intro_point_t { unsigned int cross_certified : 1; } hs_desc_intro_point_t; +/* Authorized client information located in a descriptor. */ +typedef struct hs_desc_authorized_client_t { + /* An identifier that the client will use to identify which auth client + * entry it needs to use. */ + uint8_t client_id[HS_DESC_CLIENT_ID_LEN]; + + /* An IV that is used to decrypt the encrypted descriptor cookie. */ + uint8_t iv[CIPHER_IV_LEN]; + + /* An encrypted descriptor cookie that the client needs to decrypt to use + * it to decrypt the descriptor. */ + uint8_t encrypted_cookie[HS_DESC_ENCRYPED_COOKIE_LEN]; +} hs_desc_authorized_client_t; + /* The encrypted data section of a descriptor. Obviously the data in this is * in plaintext but encrypted once encoded. */ typedef struct hs_desc_encrypted_data_t { @@ -144,6 +163,24 @@ typedef struct hs_desc_encrypted_data_t { smartlist_t *intro_points; } hs_desc_encrypted_data_t; +/* The superencrypted data section of a descriptor. Obviously the data in + * this is in plaintext but encrypted once encoded. */ +typedef struct hs_desc_superencrypted_data_t { + /* This field contains ephemeral x25519 public key which is used by + * the encryption scheme in the client authorization. */ + curve25519_public_key_t auth_ephemeral_pubkey; + + /* A list of authorized clients. Contains hs_desc_authorized_client_t + * objects. */ + smartlist_t *clients; + + /* Decoding only: The b64-decoded encrypted blob from the descriptor */ + uint8_t *encrypted_blob; + + /* Decoding only: Size of the encrypted_blob */ + size_t encrypted_blob_size; +} hs_desc_superencrypted_data_t; + /* Plaintext data that is unencrypted information of the descriptor. */ typedef struct hs_desc_plaintext_data_t { /* Version of the descriptor format. Spec specifies this field as a @@ -182,6 +219,11 @@ typedef struct hs_descriptor_t { /* Contains the plaintext part of the descriptor. */ hs_desc_plaintext_data_t plaintext_data; + /* The following contains what's in the superencrypted part of the + * descriptor. It's only encrypted in the encoded version of the descriptor + * thus the data contained in that object is in plaintext. */ + hs_desc_superencrypted_data_t superencrypted_data; + /* The following contains what's in the encrypted part of the descriptor. * It's only encrypted in the encoded version of the descriptor thus the * data contained in that object is in plaintext. */ @@ -211,6 +253,10 @@ void hs_descriptor_free_(hs_descriptor_t *desc); void hs_desc_plaintext_data_free_(hs_desc_plaintext_data_t *desc); #define hs_desc_plaintext_data_free(desc) \ FREE_AND_NULL(hs_desc_plaintext_data_t, hs_desc_plaintext_data_free_, (desc)) +void hs_desc_superencrypted_data_free_(hs_desc_superencrypted_data_t *desc); +#define hs_desc_superencrypted_data_free(desc) \ + FREE_AND_NULL(hs_desc_superencrypted_data_t, \ + hs_desc_superencrypted_data_free_, (desc)) void hs_desc_encrypted_data_free_(hs_desc_encrypted_data_t *desc); #define hs_desc_encrypted_data_free(desc) \ FREE_AND_NULL(hs_desc_encrypted_data_t, hs_desc_encrypted_data_free_, (desc)) @@ -226,14 +272,19 @@ void hs_descriptor_clear_intro_points(hs_descriptor_t *desc); MOCK_DECL(int, hs_desc_encode_descriptor,(const hs_descriptor_t *desc, const ed25519_keypair_t *signing_kp, + const uint8_t *descriptor_cookie, char **encoded_out)); int hs_desc_decode_descriptor(const char *encoded, const uint8_t *subcredential, + const curve25519_secret_key_t *client_auth_sk, hs_descriptor_t **desc_out); int hs_desc_decode_plaintext(const char *encoded, hs_desc_plaintext_data_t *plaintext); +int hs_desc_decode_superencrypted(const hs_descriptor_t *desc, + hs_desc_superencrypted_data_t *desc_out); int hs_desc_decode_encrypted(const hs_descriptor_t *desc, + const curve25519_secret_key_t *client_auth_sk, hs_desc_encrypted_data_t *desc_out); size_t hs_desc_obj_size(const hs_descriptor_t *data); @@ -243,10 +294,27 @@ hs_desc_intro_point_t *hs_desc_intro_point_new(void); void hs_desc_intro_point_free_(hs_desc_intro_point_t *ip); #define hs_desc_intro_point_free(ip) \ FREE_AND_NULL(hs_desc_intro_point_t, hs_desc_intro_point_free_, (ip)) +void hs_desc_authorized_client_free_(hs_desc_authorized_client_t *client); +#define hs_desc_authorized_client_free(client) \ + FREE_AND_NULL(hs_desc_authorized_client_t, \ + hs_desc_authorized_client_free_, (client)) link_specifier_t *hs_desc_lspec_to_trunnel( const hs_desc_link_specifier_t *spec); +hs_desc_authorized_client_t *hs_desc_build_fake_authorized_client(void); +void hs_desc_build_authorized_client(const uint8_t *subcredential, + const curve25519_public_key_t * + client_auth_pk, + const curve25519_secret_key_t * + auth_ephemeral_sk, + const uint8_t *descriptor_cookie, + hs_desc_authorized_client_t *client_out); +void hs_desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc); +void hs_desc_superencrypted_data_free_contents( + hs_desc_superencrypted_data_t *desc); +void hs_desc_encrypted_data_free_contents(hs_desc_encrypted_data_t *desc); + #ifdef HS_DESCRIPTOR_PRIVATE /* Encoding. */ @@ -265,13 +333,11 @@ STATIC int cert_is_valid(tor_cert_t *cert, uint8_t type, STATIC int desc_sig_is_valid(const char *b64_sig, const ed25519_public_key_t *signing_pubkey, const char *encoded_desc, size_t encoded_len); -STATIC size_t decode_superencrypted(const char *message, size_t message_len, - uint8_t **encrypted_out); -STATIC void desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc); MOCK_DECL(STATIC size_t, decrypt_desc_layer,(const hs_descriptor_t *desc, const uint8_t *encrypted_blob, size_t encrypted_blob_size, + const uint8_t *descriptor_cookie, int is_superencrypted_layer, char **decrypted_out)); diff --git a/src/feature/hs/hs_service.c b/src/feature/hs/hs_service.c index 30d01540f2..43e5626a57 100644 --- a/src/feature/hs/hs_service.c +++ b/src/feature/hs/hs_service.c @@ -88,6 +88,7 @@ /* Onion service directory file names. */ static const char fname_keyfile_prefix[] = "hs_ed25519"; +static const char dname_client_pubkeys[] = "authorized_clients"; static const char fname_hostname[] = "hostname"; static const char address_tld[] = "onion"; @@ -103,9 +104,16 @@ static smartlist_t *hs_service_staging_list; static int consider_republishing_hs_descriptors = 0; /* Static declaration. */ +static int load_client_keys(hs_service_t *service); static void set_descriptor_revision_counter(hs_service_descriptor_t *hs_desc, time_t now, bool is_current); +static int build_service_desc_superencrypted(const hs_service_t *service, + hs_service_descriptor_t *desc); static void move_descriptors(hs_service_t *src, hs_service_t *dst); +static int service_encode_descriptor(const hs_service_t *service, + const hs_service_descriptor_t *desc, + const ed25519_keypair_t *signing_kp, + char **encoded_out); /* Helper: Function to compare two objects in the service map. Return 1 if the * two service have the same master public identity key. */ @@ -235,7 +243,7 @@ set_service_default_config(hs_service_config_t *c, /* From a service configuration object config, clear everything from it * meaning free allocated pointers and reset the values. */ -static void +STATIC void service_clear_config(hs_service_config_t *config) { if (config == NULL) { @@ -247,6 +255,11 @@ service_clear_config(hs_service_config_t *config) rend_service_port_config_free(p);); smartlist_free(config->ports); } + if (config->clients) { + SMARTLIST_FOREACH(config->clients, hs_service_authorized_client_t *, p, + service_authorized_client_free(p)); + smartlist_free(config->clients); + } memset(config, 0, sizeof(*config)); } @@ -1070,6 +1083,11 @@ load_service_keys(hs_service_t *service) goto end; } + /* Load all client authorization keys in the service. */ + if (load_client_keys(service) < 0) { + goto end; + } + /* Succes. */ ret = 0; end: @@ -1077,6 +1095,223 @@ load_service_keys(hs_service_t *service) return ret; } +/* Check if the client file name is valid or not. Return 1 if valid, + * otherwise return 0. */ +STATIC int +client_filename_is_valid(const char *filename) +{ + int ret = 1; + const char *valid_extension = ".auth"; + + tor_assert(filename); + + /* The file extension must match and the total filename length can't be the + * length of the extension else we do not have a filename. */ + if (!strcmpend(filename, valid_extension) && + strlen(filename) != strlen(valid_extension)) { + ret = 1; + } else { + ret = 0; + } + + return ret; +} + +/* Parse an authorized client from a string. The format of a client string + * looks like (see rend-spec-v3.txt): + * + * <auth-type>:<key-type>:<base32-encoded-public-key> + * + * The <auth-type> can only be "descriptor". + * The <key-type> can only be "x25519". + * + * Return the key on success, return NULL, otherwise. */ +STATIC hs_service_authorized_client_t * +parse_authorized_client(const char *client_key_str) +{ + char *auth_type = NULL; + char *key_type = NULL; + char *pubkey_b32 = NULL; + hs_service_authorized_client_t *client = NULL; + smartlist_t *fields = smartlist_new(); + + tor_assert(client_key_str); + + smartlist_split_string(fields, client_key_str, ":", + SPLIT_SKIP_SPACE, 0); + /* Wrong number of fields. */ + if (smartlist_len(fields) != 3) { + log_warn(LD_REND, "Unknown format of client authorization file."); + goto err; + } + + auth_type = smartlist_get(fields, 0); + key_type = smartlist_get(fields, 1); + pubkey_b32 = smartlist_get(fields, 2); + + /* Currently, the only supported auth type is "descriptor". */ + if (strcmp(auth_type, "descriptor")) { + log_warn(LD_REND, "Client authorization auth type '%s' not supported.", + auth_type); + goto err; + } + + /* Currently, the only supported key type is "x25519". */ + if (strcmp(key_type, "x25519")) { + log_warn(LD_REND, "Client authorization key type '%s' not supported.", + key_type); + goto err; + } + + /* We expect a specific length of the base32 encoded key so make sure we + * have that so we don't successfully decode a value with a different length + * and end up in trouble when copying the decoded key into a fixed length + * buffer. */ + if (strlen(pubkey_b32) != BASE32_NOPAD_LEN(CURVE25519_PUBKEY_LEN)) { + log_warn(LD_REND, "Client authorization encoded base32 public key " + "length is invalid: %s", pubkey_b32); + goto err; + } + + client = tor_malloc_zero(sizeof(hs_service_authorized_client_t)); + if (base32_decode((char *) client->client_pk.public_key, + sizeof(client->client_pk.public_key), + pubkey_b32, strlen(pubkey_b32)) < 0) { + log_warn(LD_REND, "Client authorization public key cannot be decoded: %s", + pubkey_b32); + goto err; + } + + /* Success. */ + goto done; + + err: + service_authorized_client_free(client); + done: + /* It is also a good idea to wipe the public key. */ + if (pubkey_b32) { + memwipe(pubkey_b32, 0, strlen(pubkey_b32)); + } + if (fields) { + SMARTLIST_FOREACH(fields, char *, s, tor_free(s)); + smartlist_free(fields); + } + return client; +} + +/* Load all the client public keys for the given service. Return 0 on + * success else -1 on failure. */ +static int +load_client_keys(hs_service_t *service) +{ + int ret = -1; + char *client_key_str = NULL; + char *client_key_file_path = NULL; + char *client_keys_dir_path = NULL; + hs_service_config_t *config; + smartlist_t *file_list = NULL; + + tor_assert(service); + + config = &service->config; + + /* Before calling this function, we already call load_service_keys to make + * sure that the directory exists with the right permission. So, if we + * cannot create a client pubkey key directory, we consider it as a bug. */ + client_keys_dir_path = hs_path_from_filename(config->directory_path, + dname_client_pubkeys); + if (BUG(hs_check_service_private_dir(get_options()->User, + client_keys_dir_path, + config->dir_group_readable, 1) < 0)) { + goto end; + } + + /* If the list of clients already exists, we must clear it first. */ + if (config->clients) { + SMARTLIST_FOREACH(config->clients, hs_service_authorized_client_t *, p, + service_authorized_client_free(p)); + smartlist_free(config->clients); + } + + config->clients = smartlist_new(); + + file_list = tor_listdir(client_keys_dir_path); + if (file_list == NULL) { + log_warn(LD_REND, "Client authorization directory %s can't be listed.", + client_keys_dir_path); + goto end; + } + + SMARTLIST_FOREACH_BEGIN(file_list, const char *, filename) { + hs_service_authorized_client_t *client = NULL; + log_info(LD_REND, "Loading a client authorization key file %s...", + filename); + + if (!client_filename_is_valid(filename)) { + log_warn(LD_REND, "Client authorization unrecognized filename %s. " + "File must end in .auth. Ignoring.", filename); + continue; + } + + /* Create a full path for a file. */ + client_key_file_path = hs_path_from_filename(client_keys_dir_path, + filename); + client_key_str = read_file_to_str(client_key_file_path, 0, NULL); + /* Free immediately after using it. */ + tor_free(client_key_file_path); + + /* If we cannot read the file, continue with the next file. */ + if (!client_key_str) { + log_warn(LD_REND, "Client authorization file %s can't be read. " + "Corrupted or verify permission? Ignoring.", + client_key_file_path); + continue; + } + + client = parse_authorized_client(client_key_str); + /* Wipe and free immediately after using it. */ + memwipe(client_key_str, 0, strlen(client_key_str)); + tor_free(client_key_str); + + if (client) { + smartlist_add(config->clients, client); + log_info(LD_REND, "Loaded a client authorization key file %s.", + filename); + } + + } SMARTLIST_FOREACH_END(filename); + + /* If the number of clients is greater than zero, set the flag to be true. */ + if (smartlist_len(config->clients) > 0) { + config->is_client_auth_enabled = 1; + } + + /* Success. */ + ret = 0; + end: + if (client_key_str) { + memwipe(client_key_str, 0, strlen(client_key_str)); + } + if (file_list) { + SMARTLIST_FOREACH(file_list, char *, s, tor_free(s)); + smartlist_free(file_list); + } + tor_free(client_key_str); + tor_free(client_key_file_path); + tor_free(client_keys_dir_path); + return ret; +} + +STATIC void +service_authorized_client_free_(hs_service_authorized_client_t *client) +{ + if (!client) { + return; + } + memwipe(&client->client_pk, 0, sizeof(client->client_pk)); + tor_free(client); +} + /* Free a given service descriptor object and all key material is wiped. */ STATIC void service_descriptor_free_(hs_service_descriptor_t *desc) @@ -1111,8 +1346,113 @@ service_descriptor_new(void) return sdesc; } -/* Move descriptor(s) from the src service to the dst service. We do this - * during SIGHUP when we re-create our hidden services. */ +/* Allocate and return a deep copy of client. */ +static hs_service_authorized_client_t * +service_authorized_client_dup(const hs_service_authorized_client_t *client) +{ + hs_service_authorized_client_t *client_dup = NULL; + + tor_assert(client); + + client_dup = tor_malloc_zero(sizeof(hs_service_authorized_client_t)); + /* Currently, the public key is the only component of + * hs_service_authorized_client_t. */ + memcpy(client_dup->client_pk.public_key, + client->client_pk.public_key, + CURVE25519_PUBKEY_LEN); + + return client_dup; +} + +/* If two authorized clients are equal, return 0. If the first one should come + * before the second, return less than zero. If the first should come after + * the second, return greater than zero. */ +static int +service_authorized_client_cmp(const hs_service_authorized_client_t *client1, + const hs_service_authorized_client_t *client2) +{ + tor_assert(client1); + tor_assert(client2); + + /* Currently, the public key is the only component of + * hs_service_authorized_client_t. */ + return tor_memcmp(client1->client_pk.public_key, + client2->client_pk.public_key, + CURVE25519_PUBKEY_LEN); +} + +/* Helper for sorting authorized clients. */ +static int +compare_service_authorzized_client_(const void **_a, const void **_b) +{ + const hs_service_authorized_client_t *a = *_a, *b = *_b; + return service_authorized_client_cmp(a, b); +} + +/* If the list of hs_service_authorized_client_t's is different between + * src and dst, return 1. Otherwise, return 0. */ +STATIC int +service_authorized_client_config_equal(const hs_service_config_t *config1, + const hs_service_config_t *config2) +{ + int ret = 0; + int i; + smartlist_t *sl1 = smartlist_new(); + smartlist_t *sl2 = smartlist_new(); + + tor_assert(config1); + tor_assert(config2); + tor_assert(config1->clients); + tor_assert(config2->clients); + + /* If the number of clients is different, it is obvious that the list + * changes. */ + if (smartlist_len(config1->clients) != smartlist_len(config2->clients)) { + goto done; + } + + /* We do not want to mutate config1 and config2, so we will duplicate both + * entire client lists here. */ + SMARTLIST_FOREACH(config1->clients, + hs_service_authorized_client_t *, client, + smartlist_add(sl1, service_authorized_client_dup(client))); + + SMARTLIST_FOREACH(config2->clients, + hs_service_authorized_client_t *, client, + smartlist_add(sl2, service_authorized_client_dup(client))); + + smartlist_sort(sl1, compare_service_authorzized_client_); + smartlist_sort(sl2, compare_service_authorzized_client_); + + for (i = 0; i < smartlist_len(sl1); i++) { + /* If the clients at index i in both lists differ, the whole configs + * differ. */ + if (service_authorized_client_cmp(smartlist_get(sl1, i), + smartlist_get(sl2, i))) { + goto done; + } + } + + /* Success. */ + ret = 1; + + done: + if (sl1) { + SMARTLIST_FOREACH(sl1, hs_service_authorized_client_t *, p, + service_authorized_client_free(p)); + smartlist_free(sl1); + } + if (sl2) { + SMARTLIST_FOREACH(sl2, hs_service_authorized_client_t *, p, + service_authorized_client_free(p)); + smartlist_free(sl2); + } + return ret; +} + +/* Move descriptor(s) from the src service to the dst service and modify their + * content if necessary. We do this during SIGHUP when we re-create our + * hidden services. */ static void move_descriptors(hs_service_t *src, hs_service_t *dst) { @@ -1136,6 +1476,37 @@ move_descriptors(hs_service_t *src, hs_service_t *dst) dst->desc_next = src->desc_next; src->desc_next = NULL; } + + /* If the client authorization changes, we must rebuild the superencrypted + * section and republish the descriptors. */ + int client_auth_changed = + !service_authorized_client_config_equal(&src->config, &dst->config); + if (client_auth_changed && dst->desc_current) { + /* We have to clear the superencrypted content first. */ + hs_desc_superencrypted_data_free_contents( + &dst->desc_current->desc->superencrypted_data); + if (build_service_desc_superencrypted(dst, dst->desc_current) < 0) { + goto err; + } + service_desc_schedule_upload(dst->desc_current, time(NULL), 1); + } + if (client_auth_changed && dst->desc_next) { + /* We have to clear the superencrypted content first. */ + hs_desc_superencrypted_data_free_contents( + &dst->desc_next->desc->superencrypted_data); + if (build_service_desc_superencrypted(dst, dst->desc_next) < 0) { + goto err; + } + service_desc_schedule_upload(dst->desc_next, time(NULL), 1); + } + + return; + + err: + /* If there is an error, free all descriptors to make it clean and generate + * them later. */ + service_descriptor_free(dst->desc_current); + service_descriptor_free(dst->desc_next); } /* From the given service, remove all expired failing intro points for each @@ -1353,6 +1724,85 @@ build_service_desc_encrypted(const hs_service_t *service, return 0; } +/* Populate the descriptor superencrypted section from the given service + * object. This will generate a valid list of hs_desc_authorized_client_t + * of clients that are authorized to use the service. Return 0 on success + * else -1 on error. */ +static int +build_service_desc_superencrypted(const hs_service_t *service, + hs_service_descriptor_t *desc) +{ + const hs_service_config_t *config; + int i; + hs_desc_superencrypted_data_t *superencrypted; + + tor_assert(service); + tor_assert(desc); + + superencrypted = &desc->desc->superencrypted_data; + config = &service->config; + + /* The ephemeral key pair is already generated, so this should not give + * an error. */ + if (BUG(!curve25519_public_key_is_ok(&desc->auth_ephemeral_kp.pubkey))) { + return -1; + } + memcpy(&superencrypted->auth_ephemeral_pubkey, + &desc->auth_ephemeral_kp.pubkey, + sizeof(curve25519_public_key_t)); + + /* Test that subcred is not zero because we might use it below */ + if (BUG(tor_mem_is_zero((char*)desc->desc->subcredential, DIGEST256_LEN))) { + return -1; + } + + /* Create a smartlist to store clients */ + superencrypted->clients = smartlist_new(); + + /* We do not need to build the desc authorized client if the client + * authorization is disabled */ + if (config->is_client_auth_enabled) { + SMARTLIST_FOREACH_BEGIN(config->clients, + hs_service_authorized_client_t *, client) { + hs_desc_authorized_client_t *desc_client; + desc_client = tor_malloc_zero(sizeof(hs_desc_authorized_client_t)); + + /* Prepare the client for descriptor and then add to the list in the + * superencrypted part of the descriptor */ + hs_desc_build_authorized_client(desc->desc->subcredential, + &client->client_pk, + &desc->auth_ephemeral_kp.seckey, + desc->descriptor_cookie, desc_client); + smartlist_add(superencrypted->clients, desc_client); + + } SMARTLIST_FOREACH_END(client); + } + + /* We cannot let the number of auth-clients to be zero, so we need to + * make it be 16. If it is already a multiple of 16, we do not need to + * do anything. Otherwise, add the additional ones to make it a + * multiple of 16. */ + int num_clients = smartlist_len(superencrypted->clients); + int num_clients_to_add; + if (num_clients == 0) { + num_clients_to_add = HS_DESC_AUTH_CLIENT_MULTIPLE; + } else if (num_clients % HS_DESC_AUTH_CLIENT_MULTIPLE == 0) { + num_clients_to_add = 0; + } else { + num_clients_to_add = + HS_DESC_AUTH_CLIENT_MULTIPLE + - (num_clients % HS_DESC_AUTH_CLIENT_MULTIPLE); + } + + for (i = 0; i < num_clients_to_add; i++) { + hs_desc_authorized_client_t *desc_client = + hs_desc_build_fake_authorized_client(); + smartlist_add(superencrypted->clients, desc_client); + } + + return 0; +} + /* Populate the descriptor plaintext section from the given service object. * The caller must make sure that the keys in the descriptors are valid that * is are non-zero. Return 0 on success else -1 on error. */ @@ -1418,13 +1868,14 @@ generate_ope_cipher_for_desc(const hs_service_descriptor_t *hs_desc) } /* For the given service and descriptor object, create the key material which - * is the blinded keypair and the descriptor signing keypair. Return 0 on - * success else -1 on error where the generated keys MUST be ignored. */ + * is the blinded keypair, the descriptor signing keypair, the ephemeral + * keypair, and the descriptor cookie. Return 0 on success else -1 on error + * where the generated keys MUST be ignored. */ static int build_service_desc_keys(const hs_service_t *service, hs_service_descriptor_t *desc) { - int ret = 0; + int ret = -1; ed25519_keypair_t kp; tor_assert(desc); @@ -1455,9 +1906,28 @@ build_service_desc_keys(const hs_service_t *service, log_warn(LD_REND, "Can't generate descriptor signing keypair for " "service %s", safe_str_client(service->onion_address)); - ret = -1; + goto end; } + /* No need for extra strong, this is a temporary key only for this + * descriptor. Nothing long term. */ + if (curve25519_keypair_generate(&desc->auth_ephemeral_kp, 0) < 0) { + log_warn(LD_REND, "Can't generate auth ephemeral keypair for " + "service %s", + safe_str_client(service->onion_address)); + goto end; + } + + /* Random a descriptor cookie to be used as a part of a key to encrypt the + * descriptor, if the client auth is enabled. */ + if (service->config.is_client_auth_enabled) { + crypto_strongest_rand(desc->descriptor_cookie, + sizeof(desc->descriptor_cookie)); + } + + /* Success. */ + ret = 0; + end: return ret; } @@ -1491,6 +1961,10 @@ build_service_descriptor(hs_service_t *service, time_t now, if (build_service_desc_plaintext(service, desc, now) < 0) { goto err; } + /* Setup superencrypted descriptor content. */ + if (build_service_desc_superencrypted(service, desc) < 0) { + goto err; + } /* Setup encrypted descriptor content. */ if (build_service_desc_encrypted(service, desc) < 0) { goto err; @@ -1499,7 +1973,7 @@ build_service_descriptor(hs_service_t *service, time_t now, /* Let's make sure that we've created a descriptor that can actually be * encoded properly. This function also checks if the encoded output is * decodable after. */ - if (BUG(hs_desc_encode_descriptor(desc->desc, &desc->signing_kp, + if (BUG(service_encode_descriptor(service, desc, &desc->signing_kp, &encoded_desc) < 0)) { goto err; } @@ -2338,7 +2812,7 @@ upload_descriptor_to_hsdir(const hs_service_t *service, /* First of all, we'll encode the descriptor. This should NEVER fail but * just in case, let's make sure we have an actual usable descriptor. */ - if (BUG(hs_desc_encode_descriptor(desc->desc, &desc->signing_kp, + if (BUG(service_encode_descriptor(service, desc, &desc->signing_kp, &encoded_desc) < 0)) { goto end; } @@ -2904,6 +3378,34 @@ service_key_on_disk(const char *directory_path) ed25519_keypair_free(kp); tor_free(fname); + + return ret; +} + +/* This is a proxy function before actually calling hs_desc_encode_descriptor + * because we need some preprocessing here */ +static int +service_encode_descriptor(const hs_service_t *service, + const hs_service_descriptor_t *desc, + const ed25519_keypair_t *signing_kp, + char **encoded_out) +{ + int ret; + const uint8_t *descriptor_cookie = NULL; + + tor_assert(service); + tor_assert(desc); + tor_assert(encoded_out); + + /* If the client authorization is enabled, send the descriptor cookie to + * hs_desc_encode_descriptor. Otherwise, send NULL */ + if (service->config.is_client_auth_enabled) { + descriptor_cookie = desc->descriptor_cookie; + } + + ret = hs_desc_encode_descriptor(desc->desc, signing_kp, + descriptor_cookie, encoded_out); + return ret; } @@ -3114,7 +3616,8 @@ hs_service_lookup_current_desc(const ed25519_public_key_t *pk) /* No matter what is the result (which should never be a failure), return * the encoded variable, if success it will contain the right thing else * it will be NULL. */ - hs_desc_encode_descriptor(service->desc_current->desc, + service_encode_descriptor(service, + service->desc_current, &service->desc_current->signing_kp, &encoded_desc); return encoded_desc; @@ -3281,6 +3784,7 @@ hs_service_lists_fnames_for_sandbox(smartlist_t *file_list, } service_add_fnames_to_list(service, file_list); smartlist_add_strdup(dir_list, service->config.directory_path); + smartlist_add_strdup(dir_list, dname_client_pubkeys); } FOR_EACH_DESCRIPTOR_END; } @@ -3451,7 +3955,6 @@ hs_service_load_all_keys(void) if (load_service_keys(service) < 0) { goto err; } - /* XXX: Load/Generate client authorization keys. (#20700) */ } SMARTLIST_FOREACH_END(service); /* Final step, the staging list contains service in a quiescent state that diff --git a/src/feature/hs/hs_service.h b/src/feature/hs/hs_service.h index 17c654ecf4..735266071f 100644 --- a/src/feature/hs/hs_service.h +++ b/src/feature/hs/hs_service.h @@ -105,6 +105,13 @@ typedef struct hs_service_descriptor_t { * publishes the descriptor. */ hs_descriptor_t *desc; + /* Client authorization ephemeral keypair. */ + curve25519_keypair_t auth_ephemeral_kp; + + /* Descriptor cookie used to encrypt the descriptor, when the client + * authorization is enabled */ + uint8_t descriptor_cookie[HS_DESC_DESCRIPTOR_COOKIE_LEN]; + /* Descriptor signing keypair. */ ed25519_keypair_t signing_kp; @@ -148,6 +155,12 @@ typedef struct hs_service_keys_t { unsigned int is_identify_key_offline : 1; } hs_service_keys_t; +/** Service side configuration of client authorization. */ +typedef struct hs_service_authorized_client_t { + /* The client auth public key used to encrypt the descriptor cookie. */ + curve25519_public_key_t client_pk; +} hs_service_authorized_client_t; + /* Service configuration. The following are set from the torrc options either * set by the configuration file or by the control port. Nothing else should * change those values. */ @@ -176,6 +189,13 @@ typedef struct hs_service_config_t { * HiddenServiceNumIntroductionPoints option. */ unsigned int num_intro_points; + /* True iff the client auth is enabled. */ + unsigned int is_client_auth_enabled : 1; + + /* List of hs_service_authorized_client_t's of clients that may access this + * service. Specified by HiddenServiceAuthorizeClient option. */ + smartlist_t *clients; + /* True iff we allow request made on unknown ports. Specified by * HiddenServiceAllowUnknownPorts option. */ unsigned int allow_unknown_ports : 1; @@ -336,6 +356,9 @@ STATIC hs_service_descriptor_t *service_desc_find_by_intro( const hs_service_t *service, const hs_service_intro_point_t *ip); /* Helper functions. */ +STATIC int client_filename_is_valid(const char *filename); +STATIC hs_service_authorized_client_t * +parse_authorized_client(const char *client_key_str); STATIC void get_objects_from_ident(const hs_ident_circuit_t *ident, hs_service_t **service, hs_service_intro_point_t **ip, @@ -356,6 +379,13 @@ STATIC void service_descriptor_free_(hs_service_descriptor_t *desc); #define service_descriptor_free(d) \ FREE_AND_NULL(hs_service_descriptor_t, \ service_descriptor_free_, (d)) + +STATIC void +service_authorized_client_free_(hs_service_authorized_client_t *client); +#define service_authorized_client_free(c) \ + FREE_AND_NULL(hs_service_authorized_client_t, \ + service_authorized_client_free_, (c)) + STATIC int write_address_to_file(const hs_service_t *service, const char *fname_); @@ -369,6 +399,12 @@ STATIC void service_desc_schedule_upload(hs_service_descriptor_t *desc, STATIC int service_desc_hsdirs_changed(const hs_service_t *service, const hs_service_descriptor_t *desc); +STATIC int service_authorized_client_config_equal( + const hs_service_config_t *config1, + const hs_service_config_t *config2); + +STATIC void service_clear_config(hs_service_config_t *config); + #endif /* defined(HS_SERVICE_PRIVATE) */ #endif /* !defined(TOR_HS_SERVICE_H) */ |