diff options
author | Nick Mathewson <nickm@torproject.org> | 2008-08-08 14:36:11 +0000 |
---|---|---|
committer | Nick Mathewson <nickm@torproject.org> | 2008-08-08 14:36:11 +0000 |
commit | 22259a08771275acf8ee7396b9a948385750039a (patch) | |
tree | f082809203c939a48bf6c3433225145b953b14d7 | |
parent | f6879caa0447a5fd65ff07d210146393d27cb88e (diff) | |
download | tor-22259a08771275acf8ee7396b9a948385750039a.tar.gz tor-22259a08771275acf8ee7396b9a948385750039a.zip |
The first of Karsten's proposal 121 patches: configure and maintain client authorization data. Tweaked a bit: see comments on or-dev.
svn:r16475
-rw-r--r-- | ChangeLog | 3 | ||||
-rw-r--r-- | src/common/crypto.c | 55 | ||||
-rw-r--r-- | src/common/crypto.h | 7 | ||||
-rw-r--r-- | src/or/config.c | 1 | ||||
-rw-r--r-- | src/or/or.h | 21 | ||||
-rw-r--r-- | src/or/rendservice.c | 388 | ||||
-rw-r--r-- | src/or/routerparse.c | 148 |
7 files changed, 587 insertions, 36 deletions
@@ -3,6 +3,9 @@ Changes in version 0.2.1.5-alpha - 2008-08-?? - Convert many internal address representations to optionally hold IPv6 addresses. - Generate and accept IPv6 addresses in many protocol elements. + - Begin implementation of proposal 121 (Client authorization for + hidden services): associate keys, client lists, and authorization + types with hidden services. o Minor bugfixes: - Recover 3-7 bytes that were wasted per memory chunk. Fixes bug diff --git a/src/common/crypto.c b/src/common/crypto.c index 67d36d73c2..b158967a35 100644 --- a/src/common/crypto.c +++ b/src/common/crypto.c @@ -474,17 +474,14 @@ crypto_pk_read_private_key_from_filename(crypto_pk_env_t *env, return 0; } -/** PEM-encode the public key portion of <b>env</b> and write it to a - * newly allocated string. On success, set *<b>dest</b> to the new - * string, *<b>len</b> to the string's length, and return 0. On - * failure, return -1. - */ -int -crypto_pk_write_public_key_to_string(crypto_pk_env_t *env, char **dest, - size_t *len) +/** Helper function to implement crypto_pk_write_*_key_to_string. */ +static int +crypto_pk_write_key_to_string_impl(crypto_pk_env_t *env, char **dest, + size_t *len, int is_public) { BUF_MEM *buf; BIO *b; + int r; tor_assert(env); tor_assert(env->key); @@ -495,8 +492,13 @@ crypto_pk_write_public_key_to_string(crypto_pk_env_t *env, char **dest, /* 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_RSAPublicKey(b, env->key)) { - crypto_log_errors(LOG_WARN, "writing public key to string"); + if (is_public) + r = PEM_write_bio_RSAPublicKey(b, env->key); + else + r = PEM_write_bio_RSAPrivateKey(b, env->key, NULL,NULL,0,NULL,NULL); + + if (!r) { + crypto_log_errors(LOG_WARN, "writing RSA key to string"); BIO_free(b); return -1; } @@ -515,6 +517,30 @@ crypto_pk_write_public_key_to_string(crypto_pk_env_t *env, char **dest, return 0; } +/** PEM-encode the public key portion of <b>env</b> and write it to a + * newly allocated string. On success, set *<b>dest</b> to the new + * string, *<b>len</b> to the string's length, and return 0. On + * failure, return -1. + */ +int +crypto_pk_write_public_key_to_string(crypto_pk_env_t *env, char **dest, + size_t *len) +{ + return crypto_pk_write_key_to_string_impl(env, dest, len, 1); +} + +/** PEM-encode the private key portion of <b>env</b> and write it to a + * newly allocated string. On success, set *<b>dest</b> to the new + * string, *<b>len</b> 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) +{ + return crypto_pk_write_key_to_string_impl(env, dest, len, 0); +} + /** Read a PEM-encoded public key from the first <b>len</b> characters of * <b>src</b>, and store the result in <b>env</b>. Return 0 on success, -1 on * failure. @@ -593,6 +619,15 @@ crypto_pk_check_key(crypto_pk_env_t *env) return r; } +/** Return true iff <b>key</b> contains the private-key portion of the RSA + * key. */ +int +crypto_pk_key_is_private(const crypto_pk_env_t *key) +{ + tor_assert(key); + return PRIVATE_KEY_OK(key); +} + /** Compare the public-key components of a and b. Return -1 if a\<b, 0 * if a==b, and 1 if a\>b. */ diff --git a/src/common/crypto.h b/src/common/crypto.h index 64b9097f5e..a0ddd3da20 100644 --- a/src/common/crypto.h +++ b/src/common/crypto.h @@ -79,8 +79,12 @@ int crypto_pk_read_private_key_from_filename(crypto_pk_env_t *env, 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); @@ -88,6 +92,7 @@ int crypto_pk_check_key(crypto_pk_env_t *env); int crypto_pk_cmp_keys(crypto_pk_env_t *a, crypto_pk_env_t *b); size_t crypto_pk_keysize(crypto_pk_env_t *env); crypto_pk_env_t *crypto_pk_dup_key(crypto_pk_env_t *orig); +int crypto_pk_key_is_private(const crypto_pk_env_t *key); int crypto_pk_public_encrypt(crypto_pk_env_t *env, char *to, const char *from, size_t fromlen, int padding); @@ -206,8 +211,6 @@ struct evp_pkey_st *_crypto_pk_env_get_evp_pkey(crypto_pk_env_t *env, 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 diff --git a/src/or/config.c b/src/or/config.c index 0edea45bd2..e433b40336 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -226,6 +226,7 @@ static config_var_t _option_vars[] = { 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), diff --git a/src/or/or.h b/src/or/or.h index 61ae79712c..1fa7e6d7ba 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -640,6 +640,19 @@ typedef enum { * 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 @@ -3792,6 +3805,13 @@ int rend_client_send_introduction(origin_circuit_t *introcirc, /********************************* 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. */ @@ -4251,6 +4271,7 @@ int rend_decrypt_introduction_points(rend_service_descriptor_t *parsed, 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 diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 431d872249..7a20b38089 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -40,6 +40,13 @@ typedef struct rend_service_port_config_t { * rendezvous point before giving up? */ #define MAX_REND_TIMEOUT 30 +/** DOCDOC */ +typedef enum rend_auth_type_t { + REND_NO_AUTH = 0, + REND_BASIC_AUTH = 1, + REND_STEALTH_AUTH = 2, +} rend_auth_type_t; + /** Represents a single hidden service running at this OP. */ typedef struct rend_service_t { /* Fields specified in config file */ @@ -47,6 +54,10 @@ typedef struct rend_service_t { smartlist_t *ports; /**< List of rend_service_port_config_t */ int descriptor_version; /**< Rendezvous descriptor version that will be * published. */ + rend_auth_type_t 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 +90,24 @@ num_rend_services(void) return smartlist_len(rend_service_list); } +/** Helper: free storage held by a single service authorized client entry. */ +static void +rend_authorized_client_free(rend_authorized_client_t *client) +{ + if (!client) return; + if (client->client_key) + crypto_free_pk_env(client->client_key); + tor_free(client->client_name); + tor_free(client); +} + +/** Helper for strmap_free. */ +static void +rend_authorized_client_strmap_item_free(void *authorized_client) +{ + rend_authorized_client_free(authorized_client); +} + /** Release the storage held by <b>service</b>. */ static void @@ -97,6 +126,11 @@ rend_service_free(rend_service_t *service) } 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,24 +159,42 @@ rend_add_service(rend_service_t *service) 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_NO_AUTH) { + 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 = REND_NO_AUTH; + rend_add_service(v0_service); + } 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 +323,130 @@ rend_config_services(or_options_t *options, int validate_only) 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; + const char *authname; + 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; + } + authname = smartlist_get(type_names_split, 0); + if (!strcasecmp(authname, "basic") || !strcmp(authname, "1")) { + service->auth_type = REND_BASIC_AUTH; + } else if (!strcasecmp(authname, "stealth") || !strcmp(authname, "2")) { + service->auth_type = REND_STEALTH_AUTH; + } else { + 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, const char *, client_name) + { + rend_authorized_client_t *client; + size_t len = strlen(client_name); + int found_duplicate = 0; + /* XXXX proposal 121 Why 19? Also, this should be a constant. */ + 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. */ + /*XXXX proposal 121 This is O(N^2). That's not so good. */ + 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 = tor_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 == REND_BASIC_AUTH && + smartlist_len(service->clients) > 512) || + (service->auth_type == REND_STEALTH_AUTH && + 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 == REND_BASIC_AUTH ? 512 : 16, + (int)service->auth_type); + rend_service_free(service); + return -1; + } } else { smartlist_t *versions; char *version_str; @@ -351,19 +527,18 @@ rend_service_update_descriptor(rend_service_t *service) } } -/** 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) { - int i; - rend_service_t *s; + int r; 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); + SMARTLIST_FOREACH_BEGIN(rend_service_list, rend_service_t *, s) { if (s->private_key) continue; log_info(LD_REND, "Loading hidden-service keys from \"%s\"", @@ -402,10 +577,177 @@ rend_service_load_keys(void) 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; - } - return 0; + } + + /* If client authorization is configured, load or generate keys. */ + if (s->auth_type) { + char *client_keys_str = NULL; + strmap_t *parsed_clients = strmap_new(); + char cfname[512]; + FILE *cfile, *hfile; + open_file_t *open_cfile = NULL, *open_hfile = NULL; + + /* Load client keys and descriptor cookies, if available. */ + if (tor_snprintf(cfname, sizeof(cfname), "%s"PATH_SEPARATOR"client_keys", + s->directory)<0) { + log_warn(LD_CONFIG, "Directory name too long to store client keys " + "file: \"%s\".", s->directory); + goto err; + } + 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."); + goto err; + } 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 (!(cfile = start_writing_to_stdio_file(cfname, OPEN_FLAGS_REPLACE, + 0600, &open_cfile))) { + log_warn(LD_CONFIG, "Could not open client_keys file %s", + escaped(cfname)); + goto err; + } + if (!(hfile = start_writing_to_stdio_file(fname, OPEN_FLAGS_REPLACE, + 0600, &open_hfile))) { + log_warn(LD_CONFIG, "Could not open hostname file %s", escaped(fname)); + goto err; + } + + /* 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_strmap_item_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 == REND_STEALTH_AUTH) { + /* 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"); + goto err; + } + if (crypto_pk_generate_key(prkey)) { + log_warn(LD_BUG,"Error generating client key"); + goto err; + } + if (crypto_pk_check_key(prkey) <= 0) { + log_warn(LD_BUG,"Generated client key seems invalid"); + crypto_free_pk_env(prkey); + goto err; + } + 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."); + goto err; + + } + 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."); + goto err; + } + 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."); + goto err; + } + } + + if (fputs(buf, cfile) < 0) { + log_warn(LD_FS, "Could not append client entry to file: %s", + strerror(errno)); + goto err; + } + + /* Add line to hostname file. */ + if (s->auth_type == REND_BASIC_AUTH) { + /* 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."); + goto err; + } + 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); + } + + if (fputs(buf, hfile)<0) { + log_warn(LD_FS, "Could not append host entry to file: %s", + strerror(errno)); + goto err; + } + } + SMARTLIST_FOREACH_END(client); + + goto done; + err: + r = -1; + done: + tor_free(client_keys_str); + strmap_free(parsed_clients, rend_authorized_client_strmap_item_free); + if (r<0) { + abort_writing_to_file(open_cfile); + abort_writing_to_file(open_hfile); + return r; + } else { + finish_writing_to_file(open_cfile); + finish_writing_to_file(open_hfile); + } + } + } SMARTLIST_FOREACH_END(s); + return r; } /** Return the service whose public key has a digest of <b>digest</b> and diff --git a/src/or/routerparse.c b/src/or/routerparse.c index d86c6f05a9..365fe75d22 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -100,6 +100,10 @@ typedef enum { R_IPO_ONION_KEY, R_IPO_SERVICE_KEY, + C_CLIENT_NAME, + C_DESCRIPTOR_COOKIE, + C_CLIENT_KEY, + _UNRECOGNIZED, _ERR, _EOF, @@ -141,6 +145,7 @@ typedef struct directory_token_t { typedef enum { NO_OBJ, /**< No object, ever. */ NEED_OBJ, /**< Object is required. */ + NEED_SKEY_1024,/**< Object is required, and must be a 1024 bit private key */ NEED_KEY_1024, /**< Object is required, and must be a 1024 bit public key */ NEED_KEY, /**< Object is required, and must be a public key. */ OBJ_OK, /**< Object is optional. */ @@ -352,6 +357,15 @@ static token_rule_t ipo_token_table[] = { 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_SKEY_1024), + END_OF_TABLE +}; + static token_rule_t networkstatus_token_table[] = { T1("network-status-version", K_NETWORK_STATUS_VERSION, GE(1), NO_OBJ ), @@ -2789,6 +2803,7 @@ token_check_object(memarea_t *area, const char *kwd, } break; case NEED_KEY_1024: + case NEED_SKEY_1024: if (tok->key && crypto_pk_keysize(tok->key) != PK_BYTES) { tor_snprintf(ebuf, sizeof(ebuf), "Wrong size on key for %s: %d bits", kwd, (int)crypto_pk_keysize(tok->key)); @@ -2799,6 +2814,19 @@ token_check_object(memarea_t *area, const char *kwd, if (!tok->key) { tor_snprintf(ebuf, sizeof(ebuf), "Missing public key for %s", kwd); } + if (o_syn != NEED_SKEY_1024) { + if (crypto_pk_key_is_private(tok->key)) { + tor_snprintf(ebuf, sizeof(ebuf), + "Private key given for %s, which wants a public key", kwd); + RET_ERR(ebuf); + } + } else { /* o_syn == NEED_SKEY_1024 */ + if (!crypto_pk_key_is_private(tok->key)) { + tor_snprintf(ebuf, sizeof(ebuf), + "Public key given for %s, which wants a private key", kwd); + RET_ERR(ebuf); + } + } break; case OBJ_OK: break; @@ -2948,10 +2976,14 @@ get_next_token(memarea_t *area, 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 +3700,117 @@ rend_decrypt_introduction_points(rend_service_descriptor_t *parsed, return result; } +/** Parse the content of a client_key file in <b>ckstr</b> and add + * rend_authorized_client_t's for each parsed client to + * <b>parsed_clients</b>. 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); + /* XXXX proposal 121 This skips _everything_, not just comments or + * whitespace. That's no good. */ + 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 = tor_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", + escaped(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; +} + |