[or-cvs] r13441: First working implementation of simplified authorization pro (in tor/branches/121-hs-authorization: doc doc/spec src/common src/or)

kloesing at seul.org kloesing at seul.org
Sat Feb 9 10:04:44 UTC 2008


Author: kloesing
Date: 2008-02-09 05:04:44 -0500 (Sat, 09 Feb 2008)
New Revision: 13441

Modified:
   tor/branches/121-hs-authorization/doc/spec/rend-spec.txt
   tor/branches/121-hs-authorization/doc/tor.1.in
   tor/branches/121-hs-authorization/src/common/crypto.c
   tor/branches/121-hs-authorization/src/common/crypto.h
   tor/branches/121-hs-authorization/src/or/circuitlist.c
   tor/branches/121-hs-authorization/src/or/circuituse.c
   tor/branches/121-hs-authorization/src/or/config.c
   tor/branches/121-hs-authorization/src/or/connection.c
   tor/branches/121-hs-authorization/src/or/connection_edge.c
   tor/branches/121-hs-authorization/src/or/directory.c
   tor/branches/121-hs-authorization/src/or/or.h
   tor/branches/121-hs-authorization/src/or/rendclient.c
   tor/branches/121-hs-authorization/src/or/rendcommon.c
   tor/branches/121-hs-authorization/src/or/rendservice.c
   tor/branches/121-hs-authorization/src/or/routerparse.c
   tor/branches/121-hs-authorization/src/or/test.c
Log:
First working implementation of simplified authorization protocol; changes to proposal 121 pending.

Modified: tor/branches/121-hs-authorization/doc/spec/rend-spec.txt
===================================================================
--- tor/branches/121-hs-authorization/doc/spec/rend-spec.txt	2008-02-09 03:11:10 UTC (rev 13440)
+++ tor/branches/121-hs-authorization/doc/spec/rend-spec.txt	2008-02-09 10:04:44 UTC (rev 13441)
@@ -492,12 +492,11 @@
    and the client 30 minutes ahead), Bob's OP publishes the descriptor
    under the ID of both, the current and the next publication period.
 
-1.5. Alice receives a x.y.z.onion address.
+1.5. Alice receives an onion address.
 
    When Alice receives a pointer to a location-hidden service, it is as a
-   hostname of the form "z.onion" or "y.z.onion" or "x.y.z.onion", where
-   z is a base-32 encoding of a 10-octet hash of Bob's service's public
-   key, computed as follows:
+   hostname of the form "z.onion", where z is a base-32 encoding of a
+   10-octet hash of Bob's service's public key, computed as follows:
 
          1. Let H = H(PK).
          2. Let H' = the first 80 bits of H, considering each octet from
@@ -509,12 +508,11 @@
    don't need to worry about arbitrary collisions, and because it will
    make handling the url's more convenient.)
 
-   The string "x", if present, is the base-32 encoding of the
-   authentication/authorization required by the introduction point.
-   The string "y", if present, is the base-32 encoding of the
-   authentication/authorization required by the hidden service.
-   Omitting a string is taken to mean auth type [00 00].
-   See section 2 of this document for details on auth mechanisms.
+   [Formerly, onion addresses could contain authentication data for the
+    introduction point and the hidden service in the form x.y.z.onion. This
+    was never used and is also a bad idea, because in case of HTTP the
+    requested URL may be contained in the Host and Referer fields. Instead,
+    authentication data should only be written to local files. -KL]
 
    [Yes, numbers are allowed at the beginning.  See RFC 1123. -NM]
 
@@ -605,6 +603,18 @@
           KEY    Rendezvous point onion key [KLEN octets]
           RC     Rendezvous cookie            [20 octets]
           g^x    Diffie-Hellman data, part 1 [128 octets]
+        OR (in the v3 intro protocol)
+          VER    Version byte: set to 3.           [1 octet]
+          AUTHT  The auth type that is supported   [1 octet]
+          AUTHL  Length of auth data              [2 octets]
+          AUTHD  Auth data                        [variable]
+          IP     Rendezvous point's address       [4 octets]
+          PORT   Rendezvous point's OR port       [2 octets]
+          ID     Rendezvous point identity ID    [20 octets]
+          KLEN   Length of onion key              [2 octets]
+          KEY    Rendezvous point onion key    [KLEN octets]
+          RC     Rendezvous cookie               [20 octets]
+          g^x    Diffie-Hellman data, part 1    [128 octets]
 
    PK_ID is the hash of Bob's public key.  RP is NUL-padded and
    terminated. In version 0, it must contain a nickname. In version 1,
@@ -617,6 +627,8 @@
    20+42+16+20+20+128=246 bytes, and the version 1 and version 2
    introduction formats have other sizes.
 
+   AUTHL and AUTHD are only used when AUTHT is > 0.
+
    Through Tor 0.2.0.6-alpha, clients only generated the v0 introduction
    format, whereas hidden services have understood and accepted v0,
    v1, and v2 since 0.1.1.x. As of Tor 0.2.0.7-alpha and 0.1.2.18,
@@ -632,15 +644,15 @@
     "encrypted to Bob's PK" part of the introduction, but no Tors have
     ever generated these.
 
-          VER    Version byte: set to 3.           [1 octet]
+          VER    Version byte: set to 4.           [1 octet]
           ATYPE  An address type (typically 4)     [1 octet]
           ADDR   Rendezvous point's IP address     [4 or 16 octets]
-          PORT   Rendezvous point's OR port        [2 octets]
-          AUTHT  The auth type that is supported   [2 octets]
-          AUTHL  Length of auth data               [1 octet]
+          PORT   Rendezvous point's OR port       [2 octets]
+          AUTHT  The auth type that is supported   [1 octet]
+          AUTHL  Length of auth data              [2 octets]
           AUTHD  Auth data                        [variable]
           ID     Rendezvous point identity ID    [20 octets]
-          KLEN  Length of onion key               [2 octets]
+          KLEN   Length of onion key              [2 octets]
           KEY    Rendezvous point onion key    [KLEN octets]
           RC     Rendezvous cookie               [20 octets]
           g^x    Diffie-Hellman data, part 1    [128 octets]

Modified: tor/branches/121-hs-authorization/doc/tor.1.in
===================================================================
--- tor/branches/121-hs-authorization/doc/tor.1.in	2008-02-09 03:11:10 UTC (rev 13440)
+++ tor/branches/121-hs-authorization/doc/tor.1.in	2008-02-09 10:04:44 UTC (rev 13441)
@@ -1194,6 +1194,16 @@
 service. Possible version numbers are 0 and 2. (Default: 0, 2)
 .LP
 .TP
+\fBHiddenServiceAuthorizeClient \fR\fIclient-name\fR,\fIclient-name\fR,\fI...\fP
+A list of clients that are authorized to learn existence and access the
+hidden service. Valid client names are 1 to 19 characters long and only use
+characters in A-Za-z0-9+-_ (no spaces). If this option is used multiple
+times all client names are accumulated. If this option is set, the hidden
+service is not accessible for clients without authorization any more. You
+can find generated authorization data in the hostname file. See also the
+client_keys file for more information about authorization data.
+.LP
+.TP
 \fBRendPostPeriod \fR\fIN\fR \fBseconds\fR|\fBminutes\fR|\fBhours\fR|\fBdays\fR|\fBweeks\fP
 Every time the specified period elapses, Tor uploads any rendezvous
 service descriptors to the directory servers.  This information is also
@@ -1315,10 +1325,17 @@
 .TP
 .B \fIHiddenServiceDirectory\fP/hostname 
 The <base32-encoded-fingerprint>.onion domain name for this hidden service.
+If the hidden service is restricted to authorized clients only, this file
+also contains the authorization data for all clients.
 .LP
 .TP
 .B \fIHiddenServiceDirectory\fP/private_key 
 The private key for this hidden service.
+.LP
+.TP
+.B \fIHiddenServiceDirectory\fP/client_keys 
+Authorization data for a hidden service that is only accessible by authorized
+clients.
 .SH SEE ALSO
 .BR privoxy (1),
 .BR tsocks (1),

Modified: tor/branches/121-hs-authorization/src/common/crypto.c
===================================================================
--- tor/branches/121-hs-authorization/src/common/crypto.c	2008-02-09 03:11:10 UTC (rev 13440)
+++ tor/branches/121-hs-authorization/src/common/crypto.c	2008-02-09 10:04:44 UTC (rev 13441)
@@ -506,6 +506,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.

Modified: tor/branches/121-hs-authorization/src/common/crypto.h
===================================================================
--- tor/branches/121-hs-authorization/src/common/crypto.h	2008-02-09 03:11:10 UTC (rev 13440)
+++ tor/branches/121-hs-authorization/src/common/crypto.h	2008-02-09 10:04:44 UTC (rev 13441)
@@ -79,6 +79,8 @@
                                              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_write_private_key_to_filename(crypto_pk_env_t *env,
@@ -205,9 +207,9 @@
                                                 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*/
+#endif
 int crypto_pk_read_private_key_from_string(crypto_pk_env_t *env,
                                            const char *s);
-#endif
 
 #endif
 

Modified: tor/branches/121-hs-authorization/src/or/circuitlist.c
===================================================================
--- tor/branches/121-hs-authorization/src/or/circuitlist.c	2008-02-09 03:11:10 UTC (rev 13440)
+++ tor/branches/121-hs-authorization/src/or/circuitlist.c	2008-02-09 10:04:44 UTC (rev 13441)
@@ -1016,7 +1016,9 @@
              safe_str(ocirc->rend_query),
              safe_str(build_state_get_exit_nickname(ocirc->build_state)));
     rend_client_remove_intro_point(ocirc->build_state->chosen_exit,
-                                   ocirc->rend_query);
+                                   ocirc->rend_query,
+                                   (ocirc->has_desc_cookie ?
+                                    ocirc->rend_desc_cookie : NULL));
   }
   if (circ->n_conn)
     connection_or_send_destroy(circ->n_circ_id, circ->n_conn, reason);

Modified: tor/branches/121-hs-authorization/src/or/circuituse.c
===================================================================
--- tor/branches/121-hs-authorization/src/or/circuituse.c	2008-02-09 03:11:10 UTC (rev 13440)
+++ tor/branches/121-hs-authorization/src/or/circuituse.c	2008-02-09 10:04:44 UTC (rev 13441)
@@ -1026,8 +1026,14 @@
         log_info(LD_REND,
                  "No intro points for '%s': refetching service descriptor.",
                  safe_str(conn->rend_query));
-        rend_client_refetch_renddesc(conn->rend_query);
-        rend_client_refetch_v2_renddesc(conn->rend_query);
+        /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
+         * arrives first. Exception: When using a descriptor cookie, only
+         * fetch v2 descriptors.*/
+        rend_client_refetch_v2_renddesc(conn->rend_query,
+                                        (conn->has_desc_cookie ?
+                                         conn->rend_desc_cookie : NULL));
+        if (!conn->has_desc_cookie)
+          rend_client_refetch_renddesc(conn->rend_query);
         conn->_base.state = AP_CONN_STATE_RENDDESC_WAIT;
         return 0;
       }
@@ -1106,8 +1112,13 @@
       /* help predict this next time */
       rep_hist_note_used_internal(time(NULL), need_uptime, 1);
       if (circ) {
-        /* write the service_id into circ */
+        /* write the service_id (and descriptor cookie?) into circ */
         strlcpy(circ->rend_query, conn->rend_query, sizeof(circ->rend_query));
+        if (conn->has_desc_cookie) {
+          memcpy(circ->rend_desc_cookie, conn->rend_desc_cookie,
+                 REND_DESC_COOKIE_LEN);
+          circ->has_desc_cookie = 1;
+        }
         if (circ->_base.purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND &&
             circ->_base.state == CIRCUIT_STATE_OPEN)
           rend_client_rendcirc_has_opened(circ);

Modified: tor/branches/121-hs-authorization/src/or/config.c
===================================================================
--- tor/branches/121-hs-authorization/src/or/config.c	2008-02-09 03:11:10 UTC (rev 13440)
+++ tor/branches/121-hs-authorization/src/or/config.c	2008-02-09 10:04:44 UTC (rev 13441)
@@ -208,6 +208,8 @@
   VAR("HiddenServiceOptions",LINELIST_V, RendConfigLines,    NULL),
   VAR("HiddenServicePort",   LINELIST_S, RendConfigLines,    NULL),
   VAR("HiddenServiceVersion",LINELIST_S, RendConfigLines,    NULL),
+  VAR("HiddenServiceAuthorizeClient",LINELIST_S,RendConfigLines, NULL),
+  VAR("HidServAuth",             LINELIST_S, ClientSideHidServs, NULL),
   V(HSAuthoritativeDir,          BOOL,     "0"),
   V(HSAuthorityRecordStats,      BOOL,     "0"),
   V(HttpProxy,                   STRING,   NULL),
@@ -3040,6 +3042,16 @@
     options->MinUptimeHidServDirectoryV2 = 0;
   }
 
+  /* Parse client-side authorization for hidden services. */
+  if (options->ClientSideHidServs) {
+    for (cl = options->ClientSideHidServs; cl; cl = cl->next) {
+      if (!rend_parse_client_auth(cl->value)) {
+        log_warn(LD_CONFIG, "HidServAuth contains illegal value: '%s'. "
+                            "Discarding.", cl->value);
+      }
+    }
+  }
+
   if (options->RendPostPeriod < MIN_REND_POST_PERIOD) {
     log(LOG_WARN,LD_CONFIG,"RendPostPeriod option must be at least %d seconds."
         " Clipping.", MIN_REND_POST_PERIOD);

Modified: tor/branches/121-hs-authorization/src/or/connection.c
===================================================================
--- tor/branches/121-hs-authorization/src/or/connection.c	2008-02-09 03:11:10 UTC (rev 13440)
+++ tor/branches/121-hs-authorization/src/or/connection.c	2008-02-09 10:04:44 UTC (rev 13441)
@@ -477,12 +477,14 @@
         /* It's a directory connection and connecting or fetching
          * failed: forget about this router, and maybe try again. */
         connection_dir_request_failed(dir_conn);
+        /* if we were trying to fetch a v2 rend desc, retry as needed */
+        if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2)
+          rend_client_refetch_v2_renddesc(dir_conn->rend_query,
+                                          (dir_conn->has_desc_cookie ?
+                                           dir_conn->rend_desc_cookie : NULL));
       }
-      /* if we were trying to fetch a rend desc, retry as needed */
       if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC)
-        rend_client_desc_here(dir_conn->rend_query);
-      if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2)
-        rend_client_refetch_v2_renddesc(dir_conn->rend_query);
+        rend_client_desc_here(dir_conn->rend_query); /* give it a try */
       break;
     case CONN_TYPE_OR:
       or_conn = TO_OR_CONN(conn);

Modified: tor/branches/121-hs-authorization/src/or/connection_edge.c
===================================================================
--- tor/branches/121-hs-authorization/src/or/connection_edge.c	2008-02-09 03:11:10 UTC (rev 13440)
+++ tor/branches/121-hs-authorization/src/or/connection_edge.c	2008-02-09 10:04:44 UTC (rev 13441)
@@ -1455,6 +1455,7 @@
     /* it's a hidden-service request */
     rend_cache_entry_t *entry;
     int r;
+    rend_service_authorization_t *client_auth;
     tor_assert(!automap);
     if (SOCKS_COMMAND_IS_RESOLVE(socks->command)) {
       /* if it's a resolve request, fail it right now, rather than
@@ -1487,14 +1488,27 @@
       connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
       return -1;
     }
+    /* Look up if we have client authorization for it. */
+    client_auth = lookup_client_auth(conn->rend_query);
+    if (client_auth) {
+      log_info(LD_REND, "Using previously configured client authorization "
+               "for hidden service.");
+      memcpy(conn->rend_desc_cookie, client_auth->descriptor_cookie,
+             REND_DESC_COOKIE_LEN);
+      conn->has_desc_cookie = 1;
+    }
     if (r==0) {
       conn->_base.state = AP_CONN_STATE_RENDDESC_WAIT;
       log_info(LD_REND, "Unknown descriptor %s. Fetching.",
                safe_str(conn->rend_query));
       /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
-       * arrives first. */
-      rend_client_refetch_v2_renddesc(conn->rend_query);
-      rend_client_refetch_renddesc(conn->rend_query);
+       * arrives first. Exception: When using a descriptor cookie, only
+       * fetch v2 descriptors.*/
+      rend_client_refetch_v2_renddesc(conn->rend_query,
+                                      (conn->has_desc_cookie ?
+                                       conn->rend_desc_cookie : NULL));
+      if (!conn->has_desc_cookie)
+        rend_client_refetch_renddesc(conn->rend_query);
     } else { /* r > 0 */
 /** How long after we receive a hidden service descriptor do we consider
  * it valid? */
@@ -1511,9 +1525,13 @@
         log_info(LD_REND, "Stale descriptor %s. Refetching.",
                  safe_str(conn->rend_query));
         /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
-         * arrives first. */
-        rend_client_refetch_v2_renddesc(conn->rend_query);
-        rend_client_refetch_renddesc(conn->rend_query);
+         * arrives first. Exception: When using a descriptor cookie, only
+         * fetch v2 descriptors.*/
+        rend_client_refetch_v2_renddesc(conn->rend_query,
+                                        (conn->has_desc_cookie ?
+                                         conn->rend_desc_cookie : NULL));
+        if (!conn->has_desc_cookie)
+          rend_client_refetch_renddesc(conn->rend_query);
       }
     }
     return 0;

Modified: tor/branches/121-hs-authorization/src/or/directory.c
===================================================================
--- tor/branches/121-hs-authorization/src/or/directory.c	2008-02-09 03:11:10 UTC (rev 13440)
+++ tor/branches/121-hs-authorization/src/or/directory.c	2008-02-09 10:04:44 UTC (rev 13441)
@@ -892,8 +892,15 @@
     case DIR_PURPOSE_FETCH_RENDDESC_V2:
       tor_assert(resource);
       tor_assert(strlen(resource) <= REND_DESC_ID_V2_LEN_BASE32);
-      /* Remember the query to refer to it when a response arrives. */
+      /* Remember the query (and maybe descriptor cookie) to refer to it
+       * when a response arrives. (This is terrible hack part 2.)*/
       strlcpy(conn->rend_query, payload, sizeof(conn->rend_query));
+      if (strlen(payload) > REND_SERVICE_ID_LEN_BASE32) {
+        memcpy(conn->rend_desc_cookie,
+               payload + REND_SERVICE_ID_LEN_BASE32 + 1,
+               REND_DESC_COOKIE_LEN);
+        conn->has_desc_cookie = 1;
+      }
       payload = NULL;
       httpcommand = "GET";
       len = strlen(resource) + 32;
@@ -1796,7 +1803,8 @@
              (int)body_len, status_code, escaped(reason));
     switch (status_code) {
       case 200:
-        switch (rend_cache_store_v2_desc_as_client(body, NULL)) {
+        switch (rend_cache_store_v2_desc_as_client(body,
+                (conn->has_desc_cookie ? conn->rend_desc_cookie : NULL))) {
           case -2:
             log_warn(LD_REND,"Fetching v2 rendezvous descriptor failed. "
                      "Retrying at another directory.");

Modified: tor/branches/121-hs-authorization/src/or/or.h
===================================================================
--- tor/branches/121-hs-authorization/src/or/or.h	2008-02-09 03:11:10 UTC (rev 13440)
+++ tor/branches/121-hs-authorization/src/or/or.h	2008-02-09 10:04:44 UTC (rev 13441)
@@ -644,6 +644,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
 
@@ -994,6 +1007,14 @@
   /** What rendezvous service are we querying for? (AP only) */
   char rend_query[REND_SERVICE_ID_LEN_BASE32+1];
 
+  /** (Optional) descriptor cookie for requesting v2 hidden service
+   * descriptors, decrypting introduction points, and authenticating at
+   * hidden service. */
+  char rend_desc_cookie[REND_DESC_COOKIE_LEN];
+
+  /** True iff this connection contains a descriptor cookie. */
+  int has_desc_cookie:1;
+
   /** Number of times we've reassigned this application connection to
    * a new circuit. We keep track because the timeout is longer if we've
    * already retried several times. */
@@ -1048,6 +1069,14 @@
   /** What rendezvous service are we querying for? */
   char rend_query[REND_SERVICE_ID_LEN_BASE32+1];
 
+  /** (Optional) descriptor cookie for requesting v2 hidden service
+   * descriptors, decrypting introduction points, and authenticating at
+   * hidden service. */
+  char rend_desc_cookie[REND_DESC_COOKIE_LEN];
+
+  /** True iff this connection contains a descriptor cookie. */
+  int has_desc_cookie:1;
+
   char identity_digest[DIGEST_LEN]; /**< Hash of the public RSA key for
                                      * the directory server's signing key. */
 
@@ -1840,6 +1869,14 @@
    */
   char rend_query[REND_SERVICE_ID_LEN_BASE32+1];
 
+  /** (Optional) descriptor cookie for requesting v2 hidden service
+   * descriptors, decrypting introduction points, and authenticating at
+   * hidden service. */
+  char rend_desc_cookie[REND_DESC_COOKIE_LEN];
+
+  /** True iff this circuit contains a descriptor cookie. */
+  int has_desc_cookie:1;
+
   /** Stores the rendezvous descriptor version if purpose is S_*. Used to
    * distinguish introduction and rendezvous points belonging to the same
    * rendezvous service ID, but different descriptor versions.
@@ -2186,6 +2223,8 @@
   char *TestVia; /**< When reachability testing, use these as middle hop. */
   config_line_t *RendConfigLines; /**< List of configuration lines
                                           * for rendezvous services. */
+  config_line_t *ClientSideHidServs; /** List of configuration lines for
+                        * client-side authorizations for hidden services */
   char *ContactInfo; /**< Contact info to be published in the directory. */
 
   char *HttpProxy; /**< hostname[:port] to use as http proxy, if any. */
@@ -3608,9 +3647,11 @@
 int rend_client_introduction_acked(origin_circuit_t *circ, const char *request,
                                    size_t request_len);
 void rend_client_refetch_renddesc(const char *query);
-void rend_client_refetch_v2_renddesc(const char *query);
+void rend_client_refetch_v2_renddesc(const char *query,
+                                     const char *descriptor_cookie);
 int rend_client_remove_intro_point(extend_info_t *failed_intro,
-                                   const char *query);
+                                   const char *query,
+                                   const char *descriptor_cookie);
 int rend_client_rendezvous_acked(origin_circuit_t *circ, const char *request,
                                  size_t request_len);
 int rend_client_receive_rendezvous(origin_circuit_t *circ, const char *request,
@@ -3621,9 +3662,27 @@
 
 int rend_client_send_introduction(origin_circuit_t *introcirc,
                                   origin_circuit_t *rendcirc);
+int rend_parse_client_auth(char *config_line);
 
 /********************************* 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;
+  smartlist_t *access_history;
+} rend_authorized_client_t;
+
+/** Client-side configuration of authorization for a hidden service. */
+typedef struct rend_service_authorization_t {
+  char *service_name;
+  char descriptor_cookie[REND_DESC_COOKIE_LEN];
+  char onion_address[REND_SERVICE_ID_LEN_BASE32+1+5+1];
+} rend_service_authorization_t;
+
+rend_service_authorization_t *lookup_client_auth(char *onion_address);
+
 /** ASCII-encoded v2 hidden service descriptor. */
 typedef struct rend_encoded_v2_service_descriptor_t {
   char desc_id[DIGEST_LEN]; /**< Descriptor ID. */
@@ -3691,6 +3750,7 @@
 int rend_cache_size(void);
 int rend_encode_v2_descriptors(smartlist_t *descs_out,
                                rend_service_descriptor_t *desc, time_t now,
+                               crypto_pk_env_t *client_key,
                                const char *descriptor_cookie, uint8_t period);
 int rend_compute_v2_desc_id(char *desc_id_out, const char *service_id,
                             const char *descriptor_cookie,
@@ -4048,6 +4108,6 @@
                                      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/branches/121-hs-authorization/src/or/rendclient.c
===================================================================
--- tor/branches/121-hs-authorization/src/or/rendclient.c	2008-02-09 03:11:10 UTC (rev 13440)
+++ tor/branches/121-hs-authorization/src/or/rendclient.c	2008-02-09 10:04:44 UTC (rev 13441)
@@ -58,7 +58,7 @@
                               origin_circuit_t *rendcirc)
 {
   size_t payload_len;
-  int r;
+  int r, auth_shift = 0;
   char payload[RELAY_PAYLOAD_SIZE];
   char tmp[RELAY_PAYLOAD_SIZE];
   rend_cache_entry_t *entry;
@@ -117,22 +117,37 @@
     }
   }
 
+  /* if version is 3, possibly write authentication data */
+  if (entry->parsed->protocols & (1<<3)) {
+    tmp[0] = 3; /* version 3 of the cell format */
+    auth_shift = 1;
+    if (introcirc->has_desc_cookie) {
+      tmp[1] = 1;
+      set_uint16(tmp+2, htons(REND_DESC_COOKIE_LEN));
+      memcpy(tmp+4, introcirc->rend_desc_cookie, REND_DESC_COOKIE_LEN);
+      auth_shift += 2+REND_DESC_COOKIE_LEN;
+    }
+  } /* if version 2 only write version number */
+  else if (entry->parsed->protocols & (1<<2)) {
+    tmp[0] = 2; /* version 2 of the cell format */
+  }
+
   /* write the remaining items into tmp */
-  if (entry->parsed->protocols & (1<<2)) {
+  if (entry->parsed->protocols & (1<<3) || entry->parsed->protocols & (1<<2)) {
     /* version 2 format */
     extend_info_t *extend_info = rendcirc->build_state->chosen_exit;
     int klen;
-    tmp[0] = 2; /* version 2 of the cell format */
     /* nul pads */
-    set_uint32(tmp+1, htonl(extend_info->addr));
-    set_uint16(tmp+5, htons(extend_info->port));
-    memcpy(tmp+7, extend_info->identity_digest, DIGEST_LEN);
-    klen = crypto_pk_asn1_encode(extend_info->onion_key, tmp+7+DIGEST_LEN+2,
-                                 sizeof(tmp)-(7+DIGEST_LEN+2));
-    set_uint16(tmp+7+DIGEST_LEN, htons(klen));
-    memcpy(tmp+7+DIGEST_LEN+2+klen, rendcirc->rend_cookie,
+    set_uint32(tmp+auth_shift+1, htonl(extend_info->addr));
+    set_uint16(tmp+auth_shift+5, htons(extend_info->port));
+    memcpy(tmp+auth_shift+7, extend_info->identity_digest, DIGEST_LEN);
+    klen = crypto_pk_asn1_encode(extend_info->onion_key,
+                                 tmp+auth_shift+7+DIGEST_LEN+2,
+                                 sizeof(tmp)-(auth_shift+7+DIGEST_LEN+2));
+    set_uint16(tmp+auth_shift+7+DIGEST_LEN, htons(klen));
+    memcpy(tmp+auth_shift+7+DIGEST_LEN+2+klen, rendcirc->rend_cookie,
            REND_COOKIE_LEN);
-    dh_offset = 7+DIGEST_LEN+2+klen+REND_COOKIE_LEN;
+    dh_offset = auth_shift+7+DIGEST_LEN+2+klen+REND_COOKIE_LEN;
   } else {
     /* Version 0. */
     strncpy(tmp, rendcirc->build_state->chosen_exit->nickname,
@@ -241,7 +256,9 @@
      * If none remain, refetch the service descriptor.
      */
     if (rend_client_remove_intro_point(circ->build_state->chosen_exit,
-                                       circ->rend_query) > 0) {
+                                       circ->rend_query,
+                                       (circ->has_desc_cookie ?
+                                        circ->rend_desc_cookie : NULL)) > 0) {
       /* There are introduction points left. Re-extend the circuit to
        * another intro point and try again. */
       extend_info_t *extend_info;
@@ -337,12 +354,15 @@
  * descriptor, return 0, and in case of a failure -1. <b>query</b> is only
  * passed for pretty log statements. */
 static int
-directory_get_from_hs_dir(const char *desc_id, const char *query)
+directory_get_from_hs_dir(const char *desc_id, const char *query,
+                          const char *descriptor_cookie)
 {
   smartlist_t *responsible_dirs = smartlist_create();
   routerstatus_t *hs_dir;
   char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
   time_t now = time(NULL);
+  char payload[REND_SERVICE_ID_LEN_BASE32 + 1 + REND_DESC_COOKIE_LEN];
+  char descriptor_cookie_base64[3*REND_DESC_COOKIE_LEN_BASE64];
   tor_assert(desc_id);
   tor_assert(query);
   tor_assert(strlen(query) == REND_SERVICE_ID_LEN_BASE32);
@@ -376,16 +396,34 @@
    * directory now. */
   lookup_last_hid_serv_request(hs_dir, desc_id_base32, now, 1);
 
-  /* Send fetch request. (Pass query as payload to write it to the directory
-   * connection so that it can be referred to when the response arrives.) */
+  /* Send fetch request. (Pass query and possibly descriptor cookie
+   * as payload to write it to the directory connection so that it can be
+   * referred to when the response arrives. This is a terrible hack, but
+   * otherwise a lot of functions needed to be refactored just for fetching
+   * v0 and v2 hidden service descriptors) */
+  strlcpy(payload, query, REND_SERVICE_ID_LEN_BASE32+1);
+  if (descriptor_cookie) {
+    payload[REND_SERVICE_ID_LEN_BASE32] = ':';
+    memcpy(payload + REND_SERVICE_ID_LEN_BASE32 + 1, descriptor_cookie,
+           REND_DESC_COOKIE_LEN);
+    if (base64_encode(descriptor_cookie_base64, 3*REND_DESC_COOKIE_LEN_BASE64,
+                      descriptor_cookie, REND_DESC_COOKIE_LEN) < 0) {
+      log_warn(LD_BUG, "Could not base64-encode descriptor cookie.");
+      return 0;
+    }
+  }
   directory_initiate_command_routerstatus(hs_dir,
                                           DIR_PURPOSE_FETCH_RENDDESC_V2,
                                           ROUTER_PURPOSE_GENERAL,
-                                          1, desc_id_base32, query, 0, 0);
+                                          1, desc_id_base32, payload, 0, 0);
   log_info(LD_REND, "Sending fetch request for v2 descriptor for "
-                    "service '%s' with descriptor ID '%s' to hidden "
+                    "service '%s' with descriptor ID '%s' and descriptor "
+                    "cookie '%s' to hidden "
                     "service directory '%s' on port %d.",
-           query, desc_id_base32, hs_dir->nickname, hs_dir->dir_port);
+           query, desc_id_base32,
+           (descriptor_cookie == NULL ? "NULL" :
+           escaped_safe_str(descriptor_cookie_base64)),
+           hs_dir->nickname, hs_dir->dir_port);
   return 1;
 }
 
@@ -414,7 +452,8 @@
  * <b>query</b>.
  */
 void
-rend_client_refetch_v2_renddesc(const char *query)
+rend_client_refetch_v2_renddesc(const char *query,
+                                const char *descriptor_cookie)
 {
   char descriptor_id[DIGEST_LEN];
   int replicas_left_to_try[REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS];
@@ -439,13 +478,14 @@
     int chosen_replica = replicas_left_to_try[rand];
     replicas_left_to_try[rand] = replicas_left_to_try[--tries_left];
 
-    if (rend_compute_v2_desc_id(descriptor_id, query, NULL, time(NULL),
-                                chosen_replica) < 0) {
+    if (rend_compute_v2_desc_id(descriptor_id, query, descriptor_cookie,
+                                time(NULL), chosen_replica) < 0) {
       log_warn(LD_REND, "Internal error: Computing v2 rendezvous "
                         "descriptor ID did not succeed.");
       return;
     }
-    if (directory_get_from_hs_dir(descriptor_id, query) != 0)
+    if (directory_get_from_hs_dir(descriptor_id, query,
+                                  descriptor_cookie) != 0)
       return; /* either success or failure, but we're done */
   }
   /* If we come here, there are no hidden service directories left. */
@@ -462,7 +502,8 @@
  * unrecognized, 1 if recognized and some intro points remain.
  */
 int
-rend_client_remove_intro_point(extend_info_t *failed_intro, const char *query)
+rend_client_remove_intro_point(extend_info_t *failed_intro, const char *query,
+                               const char *descriptor_cookie)
 {
   int i, r;
   rend_cache_entry_t *ent;
@@ -477,9 +518,11 @@
     log_info(LD_REND, "Unknown service %s. Re-fetching descriptor.",
              escaped_safe_str(query));
     /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
-     * arrives first. */
-    rend_client_refetch_v2_renddesc(query);
-    rend_client_refetch_renddesc(query);
+     * arrives first. Exception: When using a descriptor cookie, only
+     * fetch v2 descriptors.*/
+    rend_client_refetch_v2_renddesc(query, descriptor_cookie);
+    if (!descriptor_cookie)
+      rend_client_refetch_renddesc(query);
     return 0;
   }
 
@@ -498,9 +541,11 @@
              "No more intro points remain for %s. Re-fetching descriptor.",
              escaped_safe_str(query));
     /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
-     * arrives first. */
-    rend_client_refetch_v2_renddesc(query);
-    rend_client_refetch_renddesc(query);
+     * arrives first. Exception: When using a descriptor cookie, only
+     * fetch v2 descriptors.*/
+    rend_client_refetch_v2_renddesc(query, descriptor_cookie);
+    if (!descriptor_cookie)
+      rend_client_refetch_renddesc(query);
 
     /* move all pending streams back to renddesc_wait */
     while ((conn = connection_get_by_type_state_rendquery(CONN_TYPE_AP,
@@ -692,3 +737,100 @@
   return extend_info_dup(intro->extend_info);
 }
 
+/** Client-side authorizations for hidden services; map of onion address to
+ * rend_service_authorization_t*. */
+static strmap_t *auth_hid_servs = NULL;
+
+/** Look up the client-side authorization for the hidden service with
+ * <b>onion_address</b>. Return NULL if no authorization is available for
+ * that address. */
+rend_service_authorization_t*
+lookup_client_auth(char *onion_address)
+{
+  tor_assert(onion_address);
+  if (!auth_hid_servs) return NULL;
+  return strmap_get(auth_hid_servs, onion_address);
+}
+
+/** Helper: Free storage held by rend_service_authorization_t. */
+static void
+rend_service_authorization_free(rend_service_authorization_t *auth)
+{
+  if (!auth) return;
+  tor_free(auth->service_name);
+  tor_free(auth);
+}
+
+/** Parse <b>config_line</b> as a client-side authorization for a hidden
+ * service and add it to the local map of hidden service authorizations.
+ * Return 1 for success and 0 for failure. */
+int
+rend_parse_client_auth(char *config_line)
+{
+  char *service_name, *onion_address, *descriptor_cookie;
+  char descriptor_cookie_tmp[REND_DESC_COOKIE_LEN+2];
+  smartlist_t *sl = smartlist_create();
+  rend_service_authorization_t *auth = NULL;
+  int res = 0;
+  tor_assert(config_line);
+  smartlist_split_string(sl, config_line, " ",
+                           SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
+  if (smartlist_len(sl) != 3) {
+    log_warn(LD_CONFIG, "Configuration line does not consist of "
+             "\"service-name client-key descriptor-cookie\": '%s'",
+             config_line);
+    goto free;
+  }
+  /* Parse service name (rather meant for use in a GUI controller). */
+  service_name = smartlist_get(sl, 0);
+  auth = tor_malloc_zero(sizeof(rend_service_authorization_t));
+  auth->service_name = strdup(smartlist_get(sl, 0));
+  if (auth_hid_servs && strmap_get(auth_hid_servs, auth->service_name)) {
+    log_warn(LD_CONFIG, "Duplicate service name for configuration line: "
+                        "'%s'", auth->service_name);
+    goto free;
+  }
+  /* Parse onion address. */
+  onion_address = smartlist_get(sl, 1);
+  if (strlen(onion_address) != 16+1+5 ||
+      strstr(onion_address, ".onion") != onion_address + 16) {
+    log_warn(LD_CONFIG, "Onion address has wrong format: '%s'",
+             onion_address);
+    goto free;
+  }
+  strlcpy(auth->onion_address, onion_address, 16+1);
+  if (!rend_valid_service_id(auth->onion_address)) {
+    log_warn(LD_CONFIG, "Onion address has wrong format: '%s'",
+             onion_address);
+    goto free;
+  }
+  /* Parse descriptor cookie. */
+  descriptor_cookie = smartlist_get(sl, 2);
+  if (strlen(descriptor_cookie) != 22) {
+    log_warn(LD_CONFIG, "Descriptor cookie has wrong length: '%s'",
+             descriptor_cookie);
+    goto free;
+  }
+  if (base64_decode(descriptor_cookie_tmp, REND_DESC_COOKIE_LEN+2,
+                    descriptor_cookie, strlen(descriptor_cookie)) < 0) {
+    log_warn(LD_CONFIG, "Decoding descriptor cookie failed: '%s'",
+             descriptor_cookie);
+    goto free;
+  }
+  memcpy(auth->descriptor_cookie, descriptor_cookie_tmp,
+         REND_DESC_COOKIE_LEN);
+  /* Add parsed client authorization to local map. */
+  if (!auth_hid_servs)
+    auth_hid_servs = strmap_new();
+  strmap_set(auth_hid_servs, auth->onion_address, auth);
+  auth = NULL;
+  res = 1;
+ free:
+  if (sl)
+    SMARTLIST_FOREACH(sl, char *, c, tor_free(c););
+  smartlist_free(sl);
+  if (auth)
+    rend_service_authorization_free(auth);
+  return res;
+}
+

Modified: tor/branches/121-hs-authorization/src/or/rendcommon.c
===================================================================
--- tor/branches/121-hs-authorization/src/or/rendcommon.c	2008-02-09 03:11:10 UTC (rev 13440)
+++ tor/branches/121-hs-authorization/src/or/rendcommon.c	2008-02-09 10:04:44 UTC (rev 13441)
@@ -308,15 +308,15 @@
 }
 
 /** Encode a set of rend_encoded_v2_service_descriptor_t's for <b>desc</b>
- * at time <b>now</b> using <b>descriptor_cookie</b> (may be <b>NULL</b> if
- * introduction points shall not be encrypted) and <b>period</b> (e.g. 0
- * for the current period, 1 for the next period, etc.) and add them to
- * the existing list <b>descs_out</b>; return the number of seconds that
- * the descriptors will be found by clients, or -1 if the encoding was not
- * successful. */
+ * at time <b>now</b> using <b>service_key</b>, <b>descriptor_cookie</b>
+ * (may be <b>NULL</b>), and <b>period</b> (e.g. 0 for the current period, 1
+ * for the next period, etc.) and add them to the existing list
+ * <b>descs_out</b>; return the number of seconds that the descriptors will
+ * be found by clients, or -1 if the encoding was not successful. */
 int
 rend_encode_v2_descriptors(smartlist_t *descs_out,
                            rend_service_descriptor_t *desc, time_t now,
+                           crypto_pk_env_t *service_key,
                            const char *descriptor_cookie, uint8_t period)
 {
   char service_id[DIGEST_LEN];
@@ -329,7 +329,7 @@
     return -1;
   }
   /* Obtain service_id from public key. */
-  crypto_pk_get_digest(desc->pk, service_id);
+  crypto_pk_get_digest(service_key, service_id);
   /* Calculate current time-period. */
   time_period = get_time_period(now, period, service_id);
   /* Determine how many seconds the descriptor will be valid. */
@@ -369,7 +369,7 @@
     base32_encode(desc_id_base32, sizeof(desc_id_base32),
                   enc->desc_id, DIGEST_LEN);
     /* PEM-encode the public key */
-    if (crypto_pk_write_public_key_to_string(desc->pk, &permanent_key,
+    if (crypto_pk_write_public_key_to_string(service_key, &permanent_key,
                                              &permanent_key_len) < 0) {
       log_warn(LD_BUG, "Could not write public key to string.");
       rend_encoded_v2_service_descriptor_free(enc);
@@ -437,7 +437,7 @@
     }
     if (router_append_dirobj_signature(desc_str + written,
                                        desc_len - written,
-                                       desc_digest, desc->pk) < 0) {
+                                       desc_digest, service_key) < 0) {
       log_warn(LD_BUG, "Couldn't sign desc.");
       rend_encoded_v2_service_descriptor_free(enc);
       goto err;

Modified: tor/branches/121-hs-authorization/src/or/rendservice.c
===================================================================
--- tor/branches/121-hs-authorization/src/or/rendservice.c	2008-02-09 03:11:10 UTC (rev 13440)
+++ tor/branches/121-hs-authorization/src/or/rendservice.c	2008-02-09 10:04:44 UTC (rev 13441)
@@ -62,8 +62,16 @@
   time_t next_upload_time;
   int descriptor_version; /**< Rendezvous descriptor version that will be
                            * published. */
+  smartlist_t *clients; /**< List of rend_authorized_client_t's for
+                         * clients that may access our service. */
 } rend_service_t;
 
+/** The event of a client accessing our hidden service. */
+typedef struct client_access_event_t {
+  time_t access_time;
+  char rendezvous_cookie[DIGEST_LEN];
+} client_access_event_t;
+
 /** A list of rend_service_t's for services run on this OP.
  */
 static smartlist_t *rend_service_list = NULL;
@@ -77,6 +85,23 @@
   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->access_history) {
+    SMARTLIST_FOREACH(client->access_history,
+                      client_access_event_t *, ev, tor_free(ev););
+    smartlist_free(client->access_history);
+  }
+  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 +122,11 @@
   tor_free(service->intro_exclude_nodes);
   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);
 }
 
@@ -300,6 +330,48 @@
         return -1;
       }
       service->intro_exclude_nodes = tor_strdup(line->value);
+    } else if (!strcasecmp(line->key, "HiddenServiceAuthorizeClient")) {
+      /* Parse 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 *client_names = smartlist_create();
+      smartlist_split_string(client_names, line->value, ",",
+                                   SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
+      SMARTLIST_FOREACH(client_names, char *, client_name, {
+        rend_authorized_client_t *client;
+        size_t len = strlen(client_name);
+        if (len < 1 || len > 19 ||
+            strspn(client_name, REND_LEGAL_CLIENTNAME_CHARACTERS) != len) {
+          log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains an "
+                   "illegal client name: '%s'. (Length must be between 1 "
+                   "and 19, and valid characters are [A-Za-z0-9+-_].)",
+                   client_name);
+          SMARTLIST_FOREACH(client_names, char *, cp, tor_free(cp));
+          smartlist_free(client_names);
+          rend_service_free(service);
+          return -1;
+        }
+        /* Check if client name is duplicate. */
+        if (service->clients) {
+          int found_duplicate = 0;
+          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));
+        if (!service->clients)
+          service->clients = smartlist_create();
+        client->client_name = strdup(client_name);
+        smartlist_add(service->clients, client);
+      });
+      SMARTLIST_FOREACH(client_names, char *, cp, tor_free(cp));
+      smartlist_free(client_names);
     } else {
       smartlist_t *versions;
       char *version_str;
@@ -361,8 +433,9 @@
   d->version = service->descriptor_version;
   d->intro_nodes = smartlist_create();
   /* Whoever understands descriptor version 2 also understands intro
-   * protocol 2. So we only support 2. */
-  d->protocols = 1 << 2;
+   * protocol 2. So we only support 2, not 0 anymore. */
+  /* And now we also support version 3. */
+  d->protocols = (1<<2) + (1<<3);
 
   for (i = 0; i < smartlist_len(service->intro_nodes); ++i) {
     rend_intro_point_t *intro_svc = smartlist_get(service->intro_nodes, i);
@@ -380,8 +453,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)
@@ -431,8 +505,125 @@
       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->clients) {
+      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);
+        return -1;
+      }
+      client_keys_str = read_file_to_str(cfname, RFTS_IGNORE_MISSING, NULL);
+      rend_parse_client_keys(parsed_clients, client_keys_str);
+      tor_free(client_keys_str);
+      log_info(LD_CONFIG, "Parsed %d previously stored client entries.",
+          strmap_size(parsed_clients));
+
+      /* Prepare hostname file. */
+      if (write_str_to_file(fname,
+          "# This hidden service is configured to be accessed only by "
+          "authorized\n# clients. In order to allow your clients to "
+          "access your service, provide\n# them with the onion addresses "
+          "and client keys below (excluding the #\n# character and your "
+          "chosen client name.)\n\n",0)<0) {
+        log_warn(LD_CONFIG, "Could not write initial comment to hostname "
+                            "file.");
+        return -1;
+      }
+
+      /* Either use loaded keys for configured clients or generate new
+       * ones if a client is new. */
+      SMARTLIST_FOREACH(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;
+        if ((parsed = strmap_get(parsed_clients, client->client_name))) {
+          /* Copy keys from parsed entry. */
+          client->client_key = crypto_pk_dup_key(parsed->client_key);
+          memcpy(client->descriptor_cookie, parsed->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;
+          }
+          desc_cook_out[strlen(desc_cook_out)-3] = '\0'; /* Remove == signs
+                                                            and newline. */
+        } else {
+          crypto_pk_env_t *prkey = NULL;
+          char *out;
+          size_t len;
+          char *entry;
+          size_t entry_len;
+          /* Create private key for client. */
+          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;
+          }
+          /* Create descriptor cookie for client. */
+          crypto_rand(client->descriptor_cookie, REND_DESC_COOKIE_LEN);
+          client->client_key = prkey;
+          /* Encode and append keys to client_keys file. */
+          crypto_pk_write_private_key_to_string(prkey, &out, &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;
+          }
+          desc_cook_out[strlen(desc_cook_out)-3] = '\0'; /* Remove == signs
+                                                            and newline. */
+          entry_len = 100 + strlen(client->client_name) + len;
+          entry = tor_malloc_zero(entry_len+1000);
+          if (tor_snprintf(entry, entry_len+1000,
+                                   "client-name %s\n"
+                                   "descriptor-cookie %s\n"
+                                   "client-key\n%s",
+                                 client->client_name,
+                                 desc_cook_out,
+                                 out) < 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, entry, strlen(entry), 0);
+          tor_free(entry);
+        }
+        /* Add line to hostname file. */
+        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;
+        }
+        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);
+      });
+    }
   }
   return 0;
 }
@@ -469,6 +660,95 @@
   return 0;
 }
 
+/** Check client authorization of a given <b>descriptor_cookie</b> for
+ * <b>service</b>, use <b>rendezvous_cookie</b> to detect replays or
+ * denial of service attacks, and add this request to the access history.
+ * Return 1 for success and 0 for failure. */
+static int
+rend_check_authorization(rend_service_t *service,
+                         const char *descriptor_cookie,
+                         const char *rendezvous_cookie)
+{
+  rend_authorized_client_t *auth_client = NULL;
+  client_access_event_t *event;
+  time_t now = time(NULL);
+  int num_same_rend_cookie = -1;
+  tor_assert(service);
+  tor_assert(descriptor_cookie);
+  tor_assert(rendezvous_cookie);
+  if (!service->clients) {
+    log_warn(LD_BUG, "Can't check authorization for a service that is not "
+             "configured to perform such.");
+    return 0;
+  }
+
+  /* Look up client authorization by descriptor cookie. */
+  SMARTLIST_FOREACH(service->clients, rend_authorized_client_t *, client, {
+    if (!memcmp(client->descriptor_cookie, descriptor_cookie,
+                REND_DESC_COOKIE_LEN)) {
+      auth_client = client;
+      break;
+    }
+  });
+  if (!auth_client) {
+    char descriptor_cookie_base64[3*REND_DESC_COOKIE_LEN_BASE64];
+    if (base64_encode(descriptor_cookie_base64,
+                      3*REND_DESC_COOKIE_LEN_BASE64,
+                      descriptor_cookie, REND_DESC_COOKIE_LEN) >= 0)
+      log_info(LD_REND, "No authorization found for descriptor cookie '%s'! "
+               "Dropping cell!", descriptor_cookie_base64);
+    else
+      log_info(LD_REND, "No authorization found! Access denied!");
+    return 0;
+  }
+
+  /* Add request to access history, including time and rendezvous cookie. */
+  event = tor_malloc_zero(sizeof(client_access_event_t));
+  event->access_time = now;
+  memcpy(event->rendezvous_cookie, rendezvous_cookie, DIGEST_LEN);
+  if (!auth_client->access_history)
+    auth_client->access_history = smartlist_create();
+  smartlist_add(auth_client->access_history, event);
+
+  /* Iterate over past requests, remove those which are older than one
+   * hour, and count the number of requests with same rendezvous cookie. */
+  SMARTLIST_FOREACH(auth_client->access_history, client_access_event_t *,
+                    access, {
+    if (access->access_time + 60 * 60 < now) {
+      tor_free(access);
+      SMARTLIST_DEL_CURRENT(auth_client->access_history, access);
+    } else if (!memcmp(access->rendezvous_cookie, rendezvous_cookie,
+                       DIGEST_LEN)) {
+      num_same_rend_cookie++;
+    }
+  });
+
+  /* If the total number of requests (including this request) within the
+   * last hour exceeds 10, drop this request. */
+  if (smartlist_len(auth_client->access_history) > 10) {
+    log_warn(LD_REND, "Client '%s' has exceeded the maximum number of %d "
+             "requests per hour for service '%s'. Is this an attack? "
+             "Access denied!",
+             auth_client->client_name, 10, service->service_id);
+    return 0;
+  }
+
+  /* If the number of requests with the same rendezvous cookie (including
+   * this request) exceeds 3 , drop this request. */
+  if (num_same_rend_cookie >= 3) {
+    log_warn(LD_REND, "Client '%s' has exceeded the maximum number of %d "
+             "requests using the same rendezvous cookie for service '%s'. "
+             "Is this an attack? Access denied!",
+             auth_client->client_name, 3, service->service_id);
+    return 0;
+  }
+
+  /* Allow the request. */
+  log_warn(LD_REND, "Client %s could be identified for service %s.",
+           auth_client->client_name, service->service_id);
+  return 1;
+}
+
 /******
  * Handle cells
  ******/
@@ -485,7 +765,7 @@
   char buf[RELAY_PAYLOAD_SIZE];
   char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN]; /* Holds KH, Df, Db, Kf, Kb */
   rend_service_t *service;
-  int r, i;
+  int r, i, auth_shift = 0;
   size_t len, keylen;
   crypto_dh_env_t *dh = NULL;
   origin_circuit_t *launched = NULL;
@@ -496,6 +776,9 @@
   int reason = END_CIRC_REASON_TORPROTOCOL;
   crypto_pk_env_t *intro_key;
   char intro_key_digest[DIGEST_LEN];
+  int auth_type;
+  size_t auth_len;
+  char *auth_data = NULL;
 
   base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
                 circuit->rend_pk_digest, REND_SERVICE_ID_LEN);
@@ -526,7 +809,7 @@
     return -1;
   }
 
-  /* if descriptor version is 2, use intro key instead of service key. */
+  /* if descriptor version is 2, use intro key instead of permanent key. */
   if (circuit->rend_desc_version == 0) {
     intro_key = service->private_key;
   } else {
@@ -559,33 +842,51 @@
     return -1;
   }
   len = r;
-  if (*buf == 2) {
+  if (*buf == 3) {
+    /* Version 3 INTRODUCE2 cell. */
+    auth_shift = 1;
+    auth_type = buf[1];
+    if (auth_type == 1) {
+      auth_len = ntohs(get_uint16(buf+2));
+      if (auth_len != REND_DESC_COOKIE_LEN) {
+        log_warn(LD_PROTOCOL, "Wrong auth data size %d, should be %d.",
+                 auth_len, REND_DESC_COOKIE_LEN);
+        return -1;
+      }
+      auth_data = tor_malloc_zero(REND_DESC_COOKIE_LEN);
+      memcpy(auth_data, buf+4, REND_DESC_COOKIE_LEN);
+      auth_shift += 18;
+    }
+  }
+  if (*buf == 2 || *buf == 3) {
     /* Version 2 INTRODUCE2 cell. */
     int klen;
     extend_info = tor_malloc_zero(sizeof(extend_info_t));
-    extend_info->addr = ntohl(get_uint32(buf+1));
-    extend_info->port = ntohs(get_uint16(buf+5));
-    memcpy(extend_info->identity_digest, buf+7, DIGEST_LEN);
+    extend_info->addr = ntohl(get_uint32(buf+auth_shift+1));
+    extend_info->port = ntohs(get_uint16(buf+auth_shift+5));
+    memcpy(extend_info->identity_digest, buf+auth_shift+7, DIGEST_LEN);
     extend_info->nickname[0] = '$';
     base16_encode(extend_info->nickname+1, sizeof(extend_info->nickname)-1,
                   extend_info->identity_digest, DIGEST_LEN);
 
-    klen = ntohs(get_uint16(buf+7+DIGEST_LEN));
-    if ((int)len != 7+DIGEST_LEN+2+klen+20+128) {
-      log_warn(LD_PROTOCOL, "Bad length %u for version 2 INTRODUCE2 cell.",
-               (int)len);
+    klen = ntohs(get_uint16(buf+auth_shift+7+DIGEST_LEN));
+    if ((int)len != auth_shift+7+DIGEST_LEN+2+klen+20+128) {
+      log_warn(LD_PROTOCOL, "Bad length %u for version %d INTRODUCE2 cell.",
+               (int)len, *buf);
       reason = END_CIRC_REASON_TORPROTOCOL;
       goto err;
     }
-    extend_info->onion_key = crypto_pk_asn1_decode(buf+7+DIGEST_LEN+2, klen);
+    extend_info->onion_key =
+                crypto_pk_asn1_decode(buf+auth_shift+7+DIGEST_LEN+2, klen);
     if (!extend_info->onion_key) {
       log_warn(LD_PROTOCOL,
-               "Error decoding onion key in version 2 INTRODUCE2 cell.");
+               "Error decoding onion key in version %d INTRODUCE2 cell.",
+               *buf);
       reason = END_CIRC_REASON_TORPROTOCOL;
       goto err;
     }
-    ptr = buf+7+DIGEST_LEN+2+klen;
-    len -= 7+DIGEST_LEN+2+klen;
+    ptr = buf+auth_shift+7+DIGEST_LEN+2+klen;
+    len -= auth_shift+7+DIGEST_LEN+2+klen;
   } else {
     char *rp_nickname;
     size_t nickname_field_len;
@@ -637,6 +938,18 @@
   r_cookie = ptr;
   base16_encode(hexcookie,9,r_cookie,4);
 
+  /* If the cell contains authentication data, check authorization. */
+  if (auth_data) {
+    if (service->clients) {
+      if (!rend_check_authorization(service, auth_data, r_cookie)) {
+        reason = END_CIRC_REASON_CONNECTFAILED;
+        goto err;
+      }
+    } else
+      log_info(LD_PROTOCOL, "INTRODUCE2 cell contains authentication data, "
+               "but we don't perform access control. Ignoring.");
+  }
+
   /* Try DH handshake... */
   dh = crypto_dh_new();
   if (!dh || crypto_dh_generate_public(dh)<0) {
@@ -1133,48 +1446,67 @@
     if (c && smartlist_len(c->routerstatus_list) > 0) {
       int seconds_valid;
       smartlist_t *descs = smartlist_create();
-      int i;
-      /* Encode the current descriptor. */
-      seconds_valid = rend_encode_v2_descriptors(descs, service->desc, now,
-                                                 NULL, 0);
-      if (seconds_valid < 0) {
-        log_warn(LD_BUG, "Internal error: couldn't encode service descriptor; "
-                 "not uploading.");
-        smartlist_free(descs);
-        return;
-      }
-      /* Post the current descriptors to the hidden service directories. */
-      rend_get_service_id(service->desc->pk, serviceid);
-      log_info(LD_REND, "Sending publish request for hidden service %s",
-                   serviceid);
-      directory_post_to_hs_dir(descs, serviceid, seconds_valid);
-      /* Free memory for descriptors. */
-      for (i = 0; i < smartlist_len(descs); i++)
-        rend_encoded_v2_service_descriptor_free(smartlist_get(descs, i));
-      smartlist_clear(descs);
-      /* Update next upload time. */
-      if (seconds_valid - REND_TIME_PERIOD_OVERLAPPING_V2_DESCS
-          > rendpostperiod)
-        service->next_upload_time = now + rendpostperiod;
-      else if (seconds_valid < REND_TIME_PERIOD_OVERLAPPING_V2_DESCS)
-        service->next_upload_time = now + seconds_valid + 1;
-      else
-        service->next_upload_time = now + seconds_valid -
-            REND_TIME_PERIOD_OVERLAPPING_V2_DESCS + 1;
-      /* Post also the next descriptors, if necessary. */
-      if (seconds_valid < REND_TIME_PERIOD_OVERLAPPING_V2_DESCS) {
+      int i, j;
+      /* Either upload a single descriptors (including replicas) or one
+       * descriptor for each authorized client. */
+      int num_descs = (service->clients ?
+                       smartlist_len(service->clients) : 1);
+      for (j = 0; j < num_descs; j++) {
+        char *descriptor_cookie = NULL;
+        crypto_pk_env_t *service_key = NULL;
+        if (service->clients) {
+          rend_authorized_client_t *client =
+            smartlist_get(service->clients, j);
+          descriptor_cookie = client->descriptor_cookie;
+          service_key = client->client_key;
+        } else {
+          service_key = service->private_key;
+        }
+        /* Encode the current descriptor. */
         seconds_valid = rend_encode_v2_descriptors(descs, service->desc,
-                                                   now, NULL, 1);
+                                                   now, service_key,
+                                                   descriptor_cookie, 0);
         if (seconds_valid < 0) {
           log_warn(LD_BUG, "Internal error: couldn't encode service "
                    "descriptor; not uploading.");
           smartlist_free(descs);
           return;
         }
+        /* Post the current descriptors to the hidden service directories. */
+        rend_get_service_id(service->desc->pk, serviceid);
+        log_info(LD_REND, "Sending publish request for hidden service %s",
+                     serviceid);
         directory_post_to_hs_dir(descs, serviceid, seconds_valid);
         /* Free memory for descriptors. */
         for (i = 0; i < smartlist_len(descs); i++)
           rend_encoded_v2_service_descriptor_free(smartlist_get(descs, i));
+        smartlist_clear(descs);
+        /* Update next upload time. */
+        if (seconds_valid - REND_TIME_PERIOD_OVERLAPPING_V2_DESCS
+            > rendpostperiod)
+          service->next_upload_time = now + rendpostperiod;
+        else if (seconds_valid < REND_TIME_PERIOD_OVERLAPPING_V2_DESCS)
+          service->next_upload_time = now + seconds_valid + 1;
+        else
+          service->next_upload_time = now + seconds_valid -
+              REND_TIME_PERIOD_OVERLAPPING_V2_DESCS + 1;
+        /* Post also the next descriptors, if necessary. */
+        if (seconds_valid < REND_TIME_PERIOD_OVERLAPPING_V2_DESCS) {
+          seconds_valid = rend_encode_v2_descriptors(descs, service->desc,
+                                                     now, service_key,
+                                                     descriptor_cookie, 1);
+          if (seconds_valid < 0) {
+            log_warn(LD_BUG, "Internal error: couldn't encode service "
+                     "descriptor; not uploading.");
+            smartlist_free(descs);
+            return;
+          }
+          directory_post_to_hs_dir(descs, serviceid, seconds_valid);
+          /* Free memory for descriptors. */
+          for (i = 0; i < smartlist_len(descs); i++)
+            rend_encoded_v2_service_descriptor_free(smartlist_get(descs, i));
+          smartlist_clear(descs);
+        }
       }
       smartlist_free(descs);
       uploaded = 1;

Modified: tor/branches/121-hs-authorization/src/or/routerparse.c
===================================================================
--- tor/branches/121-hs-authorization/src/or/routerparse.c	2008-02-09 03:11:10 UTC (rev 13440)
+++ tor/branches/121-hs-authorization/src/or/routerparse.c	2008-02-09 10:04:44 UTC (rev 13441)
@@ -97,6 +97,10 @@
   R_IPO_ONION_KEY,
   R_IPO_SERVICE_KEY,
 
+  C_CLIENT_NAME,
+  C_DESCRIPTOR_COOKIE,
+  C_CLIENT_KEY,
+
   _UNRECOGNIZED,
   _ERR,
   _EOF,
@@ -341,6 +345,15 @@
   END_OF_TABLE
 };
 
+/** List of tokens allowed in the (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),
+  T1("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 ),
@@ -2797,10 +2810,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 = tor_malloc(next-*s); /* really, this is too much RAM. */
@@ -3405,6 +3422,7 @@
                                              intro_points_encrypted_size);
     crypto_free_cipher_env(cipher);
     if (unenclen < 0) {
+      log_warn(LD_REND, "Decrypting introduction points failed!");
       tor_free(ipos_decrypted);
       return -1;
     }
@@ -3497,3 +3515,112 @@
   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;
+  if (!ckstr)
+    return -1;
+  tokens = smartlist_create();
+  /* Begin parsing with first entry, skipping comments or whitespace at the
+   * beginning. */
+  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);
+    /* Tokenize string. */
+    if (tokenize_string(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) < 3) {
+      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);
+    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) {
+      log_warn(LD_REND, "Descriptor cookie has illegal length: %s",
+               tok->args[0]);
+      goto err;
+    }
+    /* Add trailing == signs to make base64-decoding happy. */
+    tor_snprintf(descriptor_cookie_base64, REND_DESC_COOKIE_LEN_BASE64+2+1,
+                 "%s==", tok->args[0]);
+    /* The size of descriptor_cookie_tmp needs to REND_DESC_COOKIE_LEN+2,
+     * because a base64 of length 24 does not fit into 16 bytes in all
+     * cases. */
+    if ((base64_decode(descriptor_cookie_tmp, REND_DESC_COOKIE_LEN+2,
+                       descriptor_cookie_base64,
+                       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);
+  return result;
+}
+

Modified: tor/branches/121-hs-authorization/src/or/test.c
===================================================================
--- tor/branches/121-hs-authorization/src/or/test.c	2008-02-09 03:11:10 UTC (rev 13440)
+++ tor/branches/121-hs-authorization/src/or/test.c	2008-02-09 10:04:44 UTC (rev 13441)
@@ -3355,7 +3355,7 @@
     smartlist_add(generated->intro_nodes, intro);
   }
   test_assert(rend_encode_v2_descriptors(descs, generated, now,
-                                         NULL, 0) > 0);
+                                         NULL, NULL, 0) > 0);
   test_assert(rend_compute_v2_desc_id(computed_desc_id, service_id_base32,
                                       NULL, now, 0) == 0);
   test_memeq(((rend_encoded_v2_service_descriptor_t *)



More information about the tor-commits mailing list