diff options
Diffstat (limited to 'src/feature/hs/hs_service.c')
-rw-r--r-- | src/feature/hs/hs_service.c | 525 |
1 files changed, 514 insertions, 11 deletions
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 |