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;
+}
+