Index: /home/karsten/tor/tor-trunk/src/common/crypto.c =================================================================== --- /home/karsten/tor/tor-trunk/src/common/crypto.c (revision 16440) +++ /home/karsten/tor/tor-trunk/src/common/crypto.c (working copy) @@ -515,6 +515,47 @@ return 0; } +/** PEM-encode the private key portion of env and write it to a + * newly allocated string. On success, set *dest to the new + * string, *len to the string's length, and return 0. On + * failure, return -1. + */ +int +crypto_pk_write_private_key_to_string(crypto_pk_env_t *env, char **dest, + size_t *len) +{ + BUF_MEM *buf; + BIO *b; + + tor_assert(env); + tor_assert(env->key); + tor_assert(dest); + + b = BIO_new(BIO_s_mem()); /* Create a memory BIO */ + + /* Now you can treat b as if it were a file. Just use the + * PEM_*_bio_* functions instead of the non-bio variants. + */ + if (!PEM_write_bio_RSAPrivateKey(b, env->key, NULL,NULL,0,NULL,NULL)) { + crypto_log_errors(LOG_WARN, "writing private key to string"); + BIO_free(b); + return -1; + } + + BIO_get_mem_ptr(b, &buf); + (void)BIO_set_close(b, BIO_NOCLOSE); /* so BIO_free doesn't free buf */ + BIO_free(b); + + tor_assert(buf->length >= 0); + *dest = tor_malloc(buf->length+1); + memcpy(*dest, buf->data, buf->length); + (*dest)[buf->length] = 0; /* nul terminate it */ + *len = buf->length; + BUF_MEM_free(buf); + + return 0; +} + /** Read a PEM-encoded public key from the first len characters of * src, and store the result in env. Return 0 on success, -1 on * failure. Index: /home/karsten/tor/tor-trunk/src/common/crypto.h =================================================================== --- /home/karsten/tor/tor-trunk/src/common/crypto.h (revision 16440) +++ /home/karsten/tor/tor-trunk/src/common/crypto.h (working copy) @@ -79,8 +79,12 @@ const char *keyfile); int crypto_pk_write_public_key_to_string(crypto_pk_env_t *env, char **dest, size_t *len); +int crypto_pk_write_private_key_to_string(crypto_pk_env_t *env, + char **dest, size_t *len); int crypto_pk_read_public_key_from_string(crypto_pk_env_t *env, const char *src, size_t len); +int crypto_pk_read_private_key_from_string(crypto_pk_env_t *env, + const char *s); int crypto_pk_write_private_key_to_filename(crypto_pk_env_t *env, const char *fname); @@ -206,8 +210,6 @@ int private); struct dh_st *_crypto_dh_env_get_dh(crypto_dh_env_t *dh); /* Prototypes for private functions only used by crypto.c and test.c*/ -int crypto_pk_read_private_key_from_string(crypto_pk_env_t *env, - const char *s); void add_spaces_to_fp(char *out, size_t outlen, const char *in); #endif Index: /home/karsten/tor/tor-trunk/src/or/config.c =================================================================== --- /home/karsten/tor/tor-trunk/src/or/config.c (revision 16440) +++ /home/karsten/tor/tor-trunk/src/or/config.c (working copy) @@ -226,6 +226,7 @@ VAR("HiddenServiceOptions",LINELIST_V, RendConfigLines, NULL), VAR("HiddenServicePort", LINELIST_S, RendConfigLines, NULL), VAR("HiddenServiceVersion",LINELIST_S, RendConfigLines, NULL), + VAR("HiddenServiceAuthorizeClient",LINELIST_S,RendConfigLines, NULL), V(HSAuthoritativeDir, BOOL, "0"), V(HSAuthorityRecordStats, BOOL, "0"), V(HttpProxy, STRING, NULL), Index: /home/karsten/tor/tor-trunk/src/or/or.h =================================================================== --- /home/karsten/tor/tor-trunk/src/or/or.h (revision 16440) +++ /home/karsten/tor/tor-trunk/src/or/or.h (working copy) @@ -640,6 +640,19 @@ * identity key. */ #define REND_INTRO_POINT_ID_LEN_BASE32 32 +/** Length of the descriptor cookie that is used for client authorization + * to hidden services. */ +#define REND_DESC_COOKIE_LEN 16 + +/** Length of the base64-encoded descriptor cookie that is used for + * exchanging client authorization between hidden service and client. */ +#define REND_DESC_COOKIE_LEN_BASE64 22 + +/** Legal characters for use in authorized client names for a hidden + * service. */ +#define REND_LEGAL_CLIENTNAME_CHARACTERS \ + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-_" + #define CELL_DIRECTION_IN 1 #define CELL_DIRECTION_OUT 2 @@ -3788,6 +3801,13 @@ /********************************* rendcommon.c ***************************/ +/** Hidden-service side configuration of client authorization. */ +typedef struct rend_authorized_client_t { + char *client_name; + char descriptor_cookie[REND_DESC_COOKIE_LEN]; + crypto_pk_env_t *client_key; +} rend_authorized_client_t; + /** ASCII-encoded v2 hidden service descriptor. */ typedef struct rend_encoded_v2_service_descriptor_t { char desc_id[DIGEST_LEN]; /**< Descriptor ID. */ @@ -4247,6 +4267,7 @@ const char *descriptor_cookie, const char *intro_content, size_t intro_size); +int rend_parse_client_keys(strmap_t *parsed_clients, const char *str); #endif Index: /home/karsten/tor/tor-trunk/src/or/rendservice.c =================================================================== --- /home/karsten/tor/tor-trunk/src/or/rendservice.c (revision 16440) +++ /home/karsten/tor/tor-trunk/src/or/rendservice.c (working copy) @@ -47,6 +47,10 @@ smartlist_t *ports; /**< List of rend_service_port_config_t */ int descriptor_version; /**< Rendezvous descriptor version that will be * published. */ + int auth_type; /**< Client authorization type or 0 if no client + * authorization is performed. */ + smartlist_t *clients; /**< List of rend_authorized_client_t's of + * clients that may access our service. */ /* Other fields */ crypto_pk_env_t *private_key; /**< Permanent hidden-service key. */ char service_id[REND_SERVICE_ID_LEN_BASE32+1]; /**< Onion address without @@ -79,6 +83,18 @@ return smartlist_len(rend_service_list); } +/** Helper: free storage held by a single service authorized client entry. */ +static void +rend_authorized_client_free(void *authorized_client) +{ + rend_authorized_client_t *client = authorized_client; + if (!authorized_client) return; + if (client->client_key) + crypto_free_pk_env(client->client_key); + tor_free(client->client_name); + tor_free(client); +} + /** Release the storage held by service. */ static void @@ -97,6 +113,11 @@ } if (service->desc) rend_service_descriptor_free(service->desc); + if (service->clients) { + SMARTLIST_FOREACH(service->clients, rend_authorized_client_t *, c, + rend_authorized_client_free(c);); + smartlist_free(service->clients); + } tor_free(service); } @@ -125,20 +146,24 @@ service->intro_nodes = smartlist_create(); /* If the service is configured to publish unversioned (v0) and versioned - * descriptors (v2 or higher), split it up into two separate services. */ + * descriptors (v2 or higher), split it up into two separate services + * (unless it is configured to perform client authorization). */ if (service->descriptor_version == -1) { - rend_service_t *v0_service = tor_malloc_zero(sizeof(rend_service_t)); - v0_service->directory = tor_strdup(service->directory); - v0_service->ports = smartlist_create(); - SMARTLIST_FOREACH(service->ports, rend_service_port_config_t *, p, { - rend_service_port_config_t *copy = - tor_malloc_zero(sizeof(rend_service_port_config_t)); - memcpy(copy, p, sizeof(rend_service_port_config_t)); - smartlist_add(v0_service->ports, copy); - }); - v0_service->intro_period_started = service->intro_period_started; - v0_service->descriptor_version = 0; /* Unversioned descriptor. */ - rend_add_service(v0_service); + if (!service->auth_type) { + rend_service_t *v0_service = tor_malloc_zero(sizeof(rend_service_t)); + v0_service->directory = tor_strdup(service->directory); + v0_service->ports = smartlist_create(); + SMARTLIST_FOREACH(service->ports, rend_service_port_config_t *, p, { + rend_service_port_config_t *copy = + tor_malloc_zero(sizeof(rend_service_port_config_t)); + memcpy(copy, p, sizeof(rend_service_port_config_t)); + smartlist_add(v0_service->ports, copy); + }); + v0_service->intro_period_started = service->intro_period_started; + v0_service->descriptor_version = 0; /* Unversioned descriptor. */ + v0_service->auth_type = 0; + rend_add_service(v0_service); + } service->descriptor_version = 2; /* Versioned descriptor. */ } @@ -143,6 +168,20 @@ service->descriptor_version = 2; /* Versioned descriptor. */ } + if (service->auth_type && !service->descriptor_version) { + log_warn(LD_CONFIG, "Hidden service with client authorization and " + "version 0 descriptors configured; ignoring."); + rend_service_free(service); + return; + } + + if (service->auth_type && smartlist_len(service->clients) == 0) { + log_warn(LD_CONFIG, "Hidden service with client authorization but no " + "clients; ignoring."); + rend_service_free(service); + return; + } + if (!smartlist_len(service->ports)) { log_warn(LD_CONFIG, "Hidden service with no ports configured; ignoring."); rend_service_free(service); @@ -271,6 +310,124 @@ return -1; } smartlist_add(service->ports, portcfg); + } else if (!strcasecmp(line->key, "HiddenServiceAuthorizeClient")) { + /* Parse auth type and comma-separated list of client names and add a + * rend_authorized_client_t for each client to the service's list + * of authorized clients. */ + smartlist_t *type_names_split, *clients; + if (service->auth_type) { + log_warn(LD_CONFIG, "Got multiple HiddenServiceAuthorizeClient " + "lines for a single service."); + rend_service_free(service); + return -1; + } + type_names_split = smartlist_create(); + smartlist_split_string(type_names_split, line->value, " ", 0, 0); + if (smartlist_len(type_names_split) < 1) { + log_warn(LD_BUG, "HiddenServiceAuthorizeClient has no value. This " + "should have been prevented when parsing the " + "configuration."); + smartlist_free(type_names_split); + rend_service_free(service); + return -1; + } + service->auth_type = (int) tor_parse_long( + smartlist_get(type_names_split, 0), 10, 1, 2, NULL, NULL); + if (!service->auth_type) { + log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains " + "unrecognized auth-type '%s'. Only 1 or 2 are recognized.", + (char *) smartlist_get(type_names_split, 0)); + SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp)); + smartlist_free(type_names_split); + rend_service_free(service); + return -1; + } + service->clients = smartlist_create(); + if (smartlist_len(type_names_split) < 2) { + log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains " + "authorization type %d, but no client names.", + service->auth_type); + SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp)); + smartlist_free(type_names_split); + continue; + } + if (smartlist_len(type_names_split) > 2) { + log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains " + "illegal value '%s'. Must be formatted " + "as 'HiddenServiceAuthorizeClient auth-type " + "client-name,client-name,...' (without " + "additional spaces in comma-separated client " + "list).", + line->value); + SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp)); + smartlist_free(type_names_split); + rend_service_free(service); + return -1; + } + clients = smartlist_create(); + smartlist_split_string(clients, smartlist_get(type_names_split, 1), + ",", 0, 0); + SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp)); + smartlist_free(type_names_split); + SMARTLIST_FOREACH_BEGIN(clients, char *, client_name) + { + rend_authorized_client_t *client; + size_t len = strlen(client_name); + int found_duplicate = 0; + if (len < 1 || len > 19) { + log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains an " + "illegal client name: '%s'. Length must be " + "between 1 and 19 characters.", + client_name); + SMARTLIST_FOREACH(clients, char *, cp, tor_free(cp)); + smartlist_free(clients); + rend_service_free(service); + return -1; + } + if (strspn(client_name, REND_LEGAL_CLIENTNAME_CHARACTERS) != len) { + log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains an " + "illegal client name: '%s'. Valid " + "characters are [A-Za-z0-9+-_].", + client_name); + SMARTLIST_FOREACH(clients, char *, cp, tor_free(cp)); + smartlist_free(clients); + rend_service_free(service); + return -1; + } + /* Check if client name is duplicate. */ + SMARTLIST_FOREACH(service->clients, rend_authorized_client_t *, c, { + if (!strcmp(c->client_name, client_name)) { + log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains a " + "duplicate client name: '%s'; ignoring.", client_name); + found_duplicate = 1; + break; + } + }); + if (found_duplicate) + continue; + client = tor_malloc_zero(sizeof(rend_authorized_client_t)); + client->client_name = strdup(client_name); + smartlist_add(service->clients, client); + log_debug(LD_REND, "Adding client name '%s'", client_name); + } + SMARTLIST_FOREACH_END(client_name); + SMARTLIST_FOREACH(clients, char *, cp, tor_free(cp)); + smartlist_free(clients); + /* Ensure maximum number of clients. */ + if ((service->auth_type == 1 && + smartlist_len(service->clients) > 512) || + (service->auth_type == 2 && + smartlist_len(service->clients) > 16)) { + log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains %d " + "client authorization entries, but only a " + "maximum of %d entries is allowed for " + "authorization type %d.", + smartlist_len(service->clients), + service->auth_type == 1 ? 512 : 16, + service->auth_type); + rend_service_free(service); + return -1; + } } else { smartlist_t *versions; char *version_str; @@ -351,8 +508,9 @@ } } -/** Load and/or generate private keys for all hidden services. Return 0 on - * success, -1 on failure. +/** Load and/or generate private keys for all hidden services, possibly + * including keys for client authorization. Return 0 on success, -1 on + * failure. */ int rend_service_load_keys(void) @@ -360,7 +518,7 @@ int i; rend_service_t *s; char fname[512]; - char buf[128]; + char buf[1500]; for (i=0; i < smartlist_len(rend_service_list); ++i) { s = smartlist_get(rend_service_list,i); @@ -402,8 +560,157 @@ return -1; } tor_snprintf(buf, sizeof(buf),"%s.onion\n", s->service_id); - if (write_str_to_file(fname,buf,0)<0) + if (write_str_to_file(fname,buf,0)<0) { + log_warn(LD_CONFIG, "Could not write onion address to hostname file."); return -1; + } + + /* If client authorization is configured, load or generate keys. */ + if (s->auth_type) { + char *client_keys_str; + strmap_t *parsed_clients = strmap_new(); + char cfname[512]; + + /* Load client keys and descriptor cookies, if available. */ + if (strlcpy(cfname,s->directory,sizeof(cfname)) >= sizeof(cfname) || + strlcat(cfname,PATH_SEPARATOR"client_keys",sizeof(cfname)) + >= sizeof(cfname)) { + log_warn(LD_CONFIG, "Directory name too long to store client keys " + "file: \"%s\".", s->directory); + strmap_free(parsed_clients, rend_authorized_client_free); + return -1; + } + client_keys_str = read_file_to_str(cfname, RFTS_IGNORE_MISSING, NULL); + if (client_keys_str) { + if (rend_parse_client_keys(parsed_clients, client_keys_str) < 0) { + log_warn(LD_CONFIG, "Previously stored client_keys file could not " + "be parsed."); + tor_free(client_keys_str); + strmap_free(parsed_clients, rend_authorized_client_free); + return -1; + } else { + log_info(LD_CONFIG, "Parsed %d previously stored client entries.", + strmap_size(parsed_clients)); + tor_free(client_keys_str); + } + } + + /* Prepare client_keys and hostname files. */ + if (write_str_to_file(cfname, "", 0) < 0) { + log_warn(LD_CONFIG, "Could not clear client_keys file."); + strmap_free(parsed_clients, rend_authorized_client_free); + return -1; + } + if (write_str_to_file(fname, "", 0) < 0) { + log_warn(LD_CONFIG, "Could not clear hostname file."); + strmap_free(parsed_clients, rend_authorized_client_free); + return -1; + } + + /* Either use loaded keys for configured clients or generate new + * ones if a client is new. */ + SMARTLIST_FOREACH_BEGIN(s->clients, rend_authorized_client_t *, client) + { + char desc_cook_out[3*REND_DESC_COOKIE_LEN_BASE64+1]; + char service_id[16+1]; + rend_authorized_client_t *parsed = + strmap_get(parsed_clients, client->client_name); + int written; + size_t len; + /* Copy descriptor cookie from parsed entry or create new one. */ + if (parsed) { + memcpy(client->descriptor_cookie, parsed->descriptor_cookie, + REND_DESC_COOKIE_LEN); + } else { + crypto_rand(client->descriptor_cookie, REND_DESC_COOKIE_LEN); + } + if (base64_encode(desc_cook_out, 3*REND_DESC_COOKIE_LEN_BASE64+1, + client->descriptor_cookie, + REND_DESC_COOKIE_LEN) < 0) { + log_warn(LD_BUG, "Could not base64-encode descriptor cookie."); + strmap_free(parsed_clients, rend_authorized_client_free); + return -1; + } + /* Copy client key from parsed entry or create new one if required. */ + if (parsed && parsed->client_key) { + client->client_key = crypto_pk_dup_key(parsed->client_key); + } else if (s->auth_type == 2) { + /* Create private key for client. */ + crypto_pk_env_t *prkey = NULL; + if (!(prkey = crypto_new_pk_env())) { + log_warn(LD_BUG,"Error constructing client key"); + strmap_free(parsed_clients, rend_authorized_client_free); + return -1; + } + if (crypto_pk_generate_key(prkey)) { + log_warn(LD_BUG,"Error generating client key"); + strmap_free(parsed_clients, rend_authorized_client_free); + return -1; + } + if (crypto_pk_check_key(prkey) <= 0) { + log_warn(LD_BUG,"Generated client key seems invalid"); + crypto_free_pk_env(prkey); + strmap_free(parsed_clients, rend_authorized_client_free); + return -1; + } + client->client_key = prkey; + } + /* Add entry to client_keys file. */ + desc_cook_out[strlen(desc_cook_out)-1] = '\0'; /* Remove newline. */ + written = tor_snprintf(buf, sizeof(buf), + "client-name %s\ndescriptor-cookie %s\n", + client->client_name, desc_cook_out); + if (written < 0) { + log_warn(LD_BUG, "Could not write client entry."); + strmap_free(parsed_clients, rend_authorized_client_free); + return -1; + } + if (client->client_key) { + char *client_key_out; + crypto_pk_write_private_key_to_string(client->client_key, + &client_key_out, &len); + if (rend_get_service_id(client->client_key, service_id)<0) { + log_warn(LD_BUG, "Internal error: couldn't encode service ID."); + strmap_free(parsed_clients, rend_authorized_client_free); + return -1; + } + written = tor_snprintf(buf + written, sizeof(buf) - written, + "client-key\n%s", client_key_out); + if (written < 0) { + log_warn(LD_BUG, "Could not write client entry."); + strmap_free(parsed_clients, rend_authorized_client_free); + return -1; + } + } + append_bytes_to_file(cfname, buf, strlen(buf), 0); + /* Add line to hostname file. */ + if (s->auth_type == 1) { + /* Remove == signs (newline has been removed above). */ + desc_cook_out[strlen(desc_cook_out)-2] = '\0'; + tor_snprintf(buf, sizeof(buf),"%s.onion %s # client: %s\n", + s->service_id, desc_cook_out, client->client_name); + } else { + char extended_desc_cookie[REND_DESC_COOKIE_LEN+1]; + memcpy(extended_desc_cookie, client->descriptor_cookie, + REND_DESC_COOKIE_LEN); + extended_desc_cookie[REND_DESC_COOKIE_LEN] = (s->auth_type - 1) << 4; + if (base64_encode(desc_cook_out, 3*REND_DESC_COOKIE_LEN_BASE64+1, + extended_desc_cookie, + REND_DESC_COOKIE_LEN+1) < 0) { + log_warn(LD_BUG, "Could not base64-encode descriptor cookie."); + strmap_free(parsed_clients, rend_authorized_client_free); + return -1; + } + desc_cook_out[strlen(desc_cook_out)-3] = '\0'; /* Remove A= and + newline. */ + tor_snprintf(buf, sizeof(buf),"%s.onion %s # client: %s\n", + service_id, desc_cook_out, client->client_name); + } + append_bytes_to_file(fname, buf, strlen(buf), 0); + } + SMARTLIST_FOREACH_END(client); + strmap_free(parsed_clients, rend_authorized_client_free); + } } return 0; } Index: /home/karsten/tor/tor-trunk/src/or/routerparse.c =================================================================== --- /home/karsten/tor/tor-trunk/src/or/routerparse.c (revision 16440) +++ /home/karsten/tor/tor-trunk/src/or/routerparse.c (working copy) @@ -100,6 +100,10 @@ R_IPO_ONION_KEY, R_IPO_SERVICE_KEY, + C_CLIENT_NAME, + C_DESCRIPTOR_COOKIE, + C_CLIENT_KEY, + _UNRECOGNIZED, _ERR, _EOF, @@ -352,6 +356,15 @@ END_OF_TABLE }; +/** List of tokens allowed in the (possibly encrypted) list of introduction + * points of rendezvous service descriptors */ +static token_rule_t client_keys_token_table[] = { + T1_START("client-name", C_CLIENT_NAME, CONCAT_ARGS, NO_OBJ), + T1("descriptor-cookie", C_DESCRIPTOR_COOKIE, EQ(1), NO_OBJ), + T01("client-key", C_CLIENT_KEY, NO_ARGS, NEED_KEY_1024), + END_OF_TABLE +}; + static token_rule_t networkstatus_token_table[] = { T1("network-status-version", K_NETWORK_STATUS_VERSION, GE(1), NO_OBJ ), @@ -2948,10 +2961,14 @@ ebuf[sizeof(ebuf)-1] = '\0'; RET_ERR(ebuf); } - if (!strcmp(tok->object_type, "RSA PUBLIC KEY")) { /* If it's a key... */ + if (!strcmp(tok->object_type, "RSA PUBLIC KEY")) { /* If it's a public key */ tok->key = crypto_new_pk_env(); if (crypto_pk_read_public_key_from_string(tok->key, obstart, eol-obstart)) RET_ERR("Couldn't parse public key."); + } else if (!strcmp(tok->object_type, "RSA PRIVATE KEY")) { /* private key */ + tok->key = crypto_new_pk_env(); + if (crypto_pk_read_private_key_from_string(tok->key, obstart)) + RET_ERR("Couldn't parse private key."); } else { /* If it's something else, try to base64-decode it */ int r; tok->object_body = ALLOC(next-*s); /* really, this is too much RAM. */ @@ -3668,3 +3685,115 @@ return result; } +/** Parse the content of a client_key file in ckstr and add + * rend_authorized_client_t's for each parsed client to + * parsed_clients. Return the number of parsed clients as result + * or -1 for failure. */ +int +rend_parse_client_keys(strmap_t *parsed_clients, const char *ckstr) +{ + int result = -1; + smartlist_t *tokens; + directory_token_t *tok; + const char *current_entry = NULL; + memarea_t *area = NULL; + if (!ckstr || strlen(ckstr) == 0) + return -1; + tokens = smartlist_create(); + /* Begin parsing with first entry, skipping comments or whitespace at the + * beginning. */ + area = memarea_new(4096); + current_entry = strstr(ckstr, "client-name "); + while (!strcmpstart(current_entry, "client-name ")) { + rend_authorized_client_t *parsed_entry; + size_t len; + char descriptor_cookie_base64[REND_DESC_COOKIE_LEN_BASE64+2+1]; + char descriptor_cookie_tmp[REND_DESC_COOKIE_LEN+2]; + /* Determine end of string. */ + const char *eos = strstr(current_entry, "\nclient-name "); + if (!eos) + eos = current_entry + strlen(current_entry); + else + eos = eos + 1; + /* Free tokens and clear token list. */ + SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t)); + smartlist_clear(tokens); + memarea_clear(area); + /* Tokenize string. */ + if (tokenize_string(area, current_entry, eos, tokens, + client_keys_token_table, 0)) { + log_warn(LD_REND, "Error tokenizing client keys file."); + goto err; + } + /* Advance to next entry, if available. */ + current_entry = eos; + /* Check minimum allowed length of token list. */ + if (smartlist_len(tokens) < 2) { + log_warn(LD_REND, "Impossibly short client key entry."); + goto err; + } + /* Parse client name. */ + tok = find_first_by_keyword(tokens, C_CLIENT_NAME); + tor_assert(tok); + tor_assert(tok == smartlist_get(tokens, 0)); + tor_assert(tok->n_args == 1); + + len = strlen(tok->args[0]); + if (len < 1 || len > 19 || + strspn(tok->args[0], REND_LEGAL_CLIENTNAME_CHARACTERS) != len) { + log_warn(LD_CONFIG, "Illegal client name: %s. (Length must be " + "between 1 and 19, and valid characters are " + "[A-Za-z0-9+-_].)", tok->args[0]); + goto err; + } + /* Check if client name is duplicate. */ + if (strmap_get(parsed_clients, tok->args[0])) { + log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains a " + "duplicate client name: '%s'. Ignoring.", tok->args[0]); + goto err; + } + parsed_entry = tor_malloc_zero(sizeof(rend_authorized_client_t)); + parsed_entry->client_name = strdup(tok->args[0]); + strmap_set(parsed_clients, parsed_entry->client_name, parsed_entry); + /* Parse client key. */ + tok = find_first_by_keyword(tokens, C_CLIENT_KEY); + if (tok) { + parsed_entry->client_key = tok->key; + tok->key = NULL; /* Prevent free */ + } + + /* Parse descriptor cookie. */ + tok = find_first_by_keyword(tokens, C_DESCRIPTOR_COOKIE); + tor_assert(tok); + tor_assert(tok->n_args == 1); + if (strlen(tok->args[0]) != REND_DESC_COOKIE_LEN_BASE64 + 2) { + log_warn(LD_REND, "Descriptor cookie has illegal length: %s", + tok->args[0]); + goto err; + } + /* The size of descriptor_cookie_tmp needs to be REND_DESC_COOKIE_LEN+2, + * because a base64 encoding of length 24 does not fit into 16 bytes in all + * cases. */ + if ((base64_decode(descriptor_cookie_tmp, REND_DESC_COOKIE_LEN+2, + tok->args[0], REND_DESC_COOKIE_LEN_BASE64+2+1) + != REND_DESC_COOKIE_LEN)) { + log_warn(LD_REND, "Descriptor cookie contains illegal characters: " + "%s", descriptor_cookie_base64); + goto err; + } + memcpy(parsed_entry->descriptor_cookie, descriptor_cookie_tmp, + REND_DESC_COOKIE_LEN); + } + result = strmap_size(parsed_clients); + goto done; + err: + result = -1; + done: + /* Free tokens and clear token list. */ + SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t)); + smartlist_free(tokens); + if (area) + memarea_drop_all(area); + return result; +} +