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

[or-cvs] r16475: {tor} The first of Karsten's proposal 121 patches: configure and m (in tor/trunk: . src/common src/or)



Author: nickm
Date: 2008-08-08 10:36:11 -0400 (Fri, 08 Aug 2008)
New Revision: 16475

Modified:
   tor/trunk/ChangeLog
   tor/trunk/src/common/crypto.c
   tor/trunk/src/common/crypto.h
   tor/trunk/src/or/config.c
   tor/trunk/src/or/or.h
   tor/trunk/src/or/rendservice.c
   tor/trunk/src/or/routerparse.c
Log:
The first of Karsten's proposal 121 patches: configure and maintain client authorization data.  Tweaked a bit: see comments on or-dev.

Modified: tor/trunk/ChangeLog
===================================================================
--- tor/trunk/ChangeLog	2008-08-08 12:58:17 UTC (rev 16474)
+++ tor/trunk/ChangeLog	2008-08-08 14:36:11 UTC (rev 16475)
@@ -3,6 +3,9 @@
     - 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

Modified: tor/trunk/src/common/crypto.c
===================================================================
--- tor/trunk/src/common/crypto.c	2008-08-08 12:58:17 UTC (rev 16474)
+++ tor/trunk/src/common/crypto.c	2008-08-08 14:36:11 UTC (rev 16475)
@@ -474,17 +474,14 @@
   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 @@
   /* 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 @@
   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 @@
   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.
  */

Modified: tor/trunk/src/common/crypto.h
===================================================================
--- tor/trunk/src/common/crypto.h	2008-08-08 12:58:17 UTC (rev 16474)
+++ tor/trunk/src/common/crypto.h	2008-08-08 14:36:11 UTC (rev 16475)
@@ -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);
 
@@ -88,6 +92,7 @@
 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 @@
                                                 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
 

Modified: tor/trunk/src/or/config.c
===================================================================
--- tor/trunk/src/or/config.c	2008-08-08 12:58:17 UTC (rev 16474)
+++ tor/trunk/src/or/config.c	2008-08-08 14:36:11 UTC (rev 16475)
@@ -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),

Modified: tor/trunk/src/or/or.h
===================================================================
--- tor/trunk/src/or/or.h	2008-08-08 12:58:17 UTC (rev 16474)
+++ tor/trunk/src/or/or.h	2008-08-08 14:36:11 UTC (rev 16475)
@@ -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
 
@@ -3792,6 +3805,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. */
@@ -4251,6 +4271,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
 

Modified: tor/trunk/src/or/rendservice.c
===================================================================
--- tor/trunk/src/or/rendservice.c	2008-08-08 12:58:17 UTC (rev 16474)
+++ tor/trunk/src/or/rendservice.c	2008-08-08 14:36:11 UTC (rev 16475)
@@ -40,6 +40,13 @@
  * 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 @@
   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 @@
   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 @@
   }
   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 @@
   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 @@
         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 @@
   }
 }
 
-/** 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 @@
       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

Modified: tor/trunk/src/or/routerparse.c
===================================================================
--- tor/trunk/src/or/routerparse.c	2008-08-08 12:58:17 UTC (rev 16474)
+++ tor/trunk/src/or/routerparse.c	2008-08-08 14:36:11 UTC (rev 16475)
@@ -100,6 +100,10 @@
   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 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 @@
   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 @@
       }
       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 @@
       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 @@
     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 @@
   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;
+}
+