[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

First patch for proposal 121



-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi Nick,

as discussed on IRC I have started splitting up the implementation of
proposal 121 into smaller patches. I've come to the conclusion that four
main patches would be a good trade-off between patch size and number in
this case. There might be further, smaller patches afterwards, but these
four patches cover roughly everything to make proposal 121 work.

1. This is the first patch that includes configuration of client
authorization on hidden-service side, but without actually using that
data when advertising hidden services.

2. The second patch will use client authorization data to advertise
hidden services and restrict access to authorized clients only.

3. The third patch will enable users to configure authorization data for
hidden services on client side.

4. Finally, the fourth patch uses authorization data to access a hidden
service.

Let me know what parts of this first patch I should improve (or if you
want more/smaller or less/larger patches in the future).

Thanks!
- --Karsten
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFImayI0M+WPffBEmURAh1ZAKDN7JD41LzQSEfRO4Fi3r/7yG5oOwCfQp3G
L84XuhLl4wbaGVbmMWqmXXE=
=tbfV
-----END PGP SIGNATURE-----
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 <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)
+{
+  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 <b>len</b> characters of
  * <b>src</b>, and store the result in <b>env</b>.  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 <b>service</b>.
  */
 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 <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);
+  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;
+}
+

Attachment: patch-121-1.txt.sig
Description: Binary data