Browse Source

Add client auth for ADD_ONION services

John Brooks 9 years ago
parent
commit
dcc11674db
3 changed files with 203 additions and 21 deletions
  1. 150 21
      src/or/control.c
  2. 2 0
      src/or/control.h
  3. 51 0
      src/test/test_controller.c

+ 150 - 21
src/or/control.c

@@ -3745,14 +3745,18 @@ handle_control_add_onion(control_connection_t *conn,
    * the other arguments are malformed.
    */
   smartlist_t *port_cfgs = smartlist_new();
+  smartlist_t *auth_clients = NULL;
+  smartlist_t *auth_created_clients = NULL;
   int discard_pk = 0;
   int detach = 0;
   int max_streams = 0;
   int max_streams_close_circuit = 0;
+  rend_auth_type_t auth_type = REND_NO_AUTH;
   for (size_t i = 1; i < arg_len; i++) {
     static const char *port_prefix = "Port=";
     static const char *flags_prefix = "Flags=";
     static const char *max_s_prefix = "MaxStreams=";
+    static const char *auth_prefix = "ClientAuth=";
 
     const char *arg = smartlist_get(args, i);
     if (!strcasecmpstart(arg, port_prefix)) {
@@ -3783,10 +3787,12 @@ handle_control_add_onion(control_connection_t *conn,
        *                connection.
        *   * 'MaxStreamsCloseCircuit' - Close the circuit if MaxStreams is
        *                                exceeded.
+       *   * 'BasicAuth' - Client authorization using the 'basic' method.
        */
       static const char *discard_flag = "DiscardPK";
       static const char *detach_flag = "Detach";
       static const char *max_s_close_flag = "MaxStreamsCloseCircuit";
+      static const char *basicauth_flag = "BasicAuth";
 
       smartlist_t *flags = smartlist_new();
       int bad = 0;
@@ -3805,6 +3811,8 @@ handle_control_add_onion(control_connection_t *conn,
           detach = 1;
         } else if (!strcasecmp(flag, max_s_close_flag)) {
           max_streams_close_circuit = 1;
+        } else if (!strcasecmp(flag, basicauth_flag)) {
+          auth_type = REND_BASIC_AUTH;
         } else {
           connection_printf_to_buf(conn,
                                    "512 Invalid 'Flags' argument: %s\r\n",
@@ -3817,6 +3825,42 @@ handle_control_add_onion(control_connection_t *conn,
       smartlist_free(flags);
       if (bad)
         goto out;
+    } else if (!strcasecmpstart(arg, auth_prefix)) {
+      char *err_msg = NULL;
+      int created = 0;
+      rend_authorized_client_t *client =
+        add_onion_helper_clientauth(arg + strlen(auth_prefix),
+                                    &created, &err_msg);
+      if (!client) {
+        if (err_msg) {
+          connection_write_str_to_buf(err_msg, conn);
+          tor_free(err_msg);
+        }
+        goto out;
+      }
+
+      if (auth_clients != NULL) {
+        int bad = 0;
+        SMARTLIST_FOREACH_BEGIN(auth_clients, rend_authorized_client_t *, ac) {
+          if (strcmp(ac->client_name, client->client_name) == 0) {
+            bad = 1;
+            break;
+          }
+        } SMARTLIST_FOREACH_END(ac);
+        if (bad) {
+          connection_printf_to_buf(conn,
+                                   "512 Duplicate name in ClientAuth\r\n");
+          rend_authorized_client_free(client);
+          goto out;
+        }
+      } else {
+        auth_clients = smartlist_new();
+        auth_created_clients = smartlist_new();
+      }
+      smartlist_add(auth_clients, client);
+      if (created) {
+        smartlist_add(auth_created_clients, client);
+      }
     } else {
       connection_printf_to_buf(conn, "513 Invalid argument\r\n");
       goto out;
@@ -3825,6 +3869,18 @@ handle_control_add_onion(control_connection_t *conn,
   if (smartlist_len(port_cfgs) == 0) {
     connection_printf_to_buf(conn, "512 Missing 'Port' argument\r\n");
     goto out;
+  } else if (auth_type == REND_NO_AUTH && auth_clients != NULL) {
+    connection_printf_to_buf(conn, "512 No auth type specified\r\n");
+    goto out;
+  } else if (auth_type != REND_NO_AUTH && auth_clients == NULL) {
+    connection_printf_to_buf(conn, "512 No auth clients specified\r\n");
+    goto out;
+  } else if ((auth_type == REND_BASIC_AUTH &&
+              smartlist_len(auth_clients) > 512) ||
+             (auth_type == REND_STEALTH_AUTH &&
+              smartlist_len(auth_clients) > 16)) {
+    connection_printf_to_buf(conn, "512 Too many auth clients\r\n");
+    goto out;
   }
 
   /* Parse the "keytype:keyblob" argument. */
@@ -3853,29 +3909,13 @@ handle_control_add_onion(control_connection_t *conn,
   char *service_id = NULL;
   int ret = rend_service_add_ephemeral(pk, port_cfgs, max_streams,
                                        max_streams_close_circuit,
-                                       REND_NO_AUTH, NULL,
+                                       auth_type, auth_clients,
                                        &service_id);
   port_cfgs = NULL; /* port_cfgs is now owned by the rendservice code. */
+  auth_clients = NULL; /* so is auth_clients */
   switch (ret) {
   case RSAE_OKAY:
   {
-    char *buf = NULL;
-    tor_assert(service_id);
-    if (key_new_alg) {
-      tor_assert(key_new_blob);
-      tor_asprintf(&buf,
-                   "250-ServiceID=%s\r\n"
-                   "250-PrivateKey=%s:%s\r\n"
-                   "250 OK\r\n",
-                   service_id,
-                   key_new_alg,
-                   key_new_blob);
-    } else {
-      tor_asprintf(&buf,
-                   "250-ServiceID=%s\r\n"
-                   "250 OK\r\n",
-                   service_id);
-    }
     if (detach) {
       if (!detached_onion_services)
         detached_onion_services = smartlist_new();
@@ -3886,9 +3926,26 @@ handle_control_add_onion(control_connection_t *conn,
       smartlist_add(conn->ephemeral_onion_services, service_id);
     }
 
-    connection_write_str_to_buf(buf, conn);
-    memwipe(buf, 0, strlen(buf));
-    tor_free(buf);
+    tor_assert(service_id);
+    connection_printf_to_buf(conn, "250-ServiceID=%s\r\n", service_id);
+    if (key_new_alg) {
+      tor_assert(key_new_blob);
+      connection_printf_to_buf(conn, "250-PrivateKey=%s:%s\r\n",
+                               key_new_alg, key_new_blob);
+    }
+    if (auth_created_clients) {
+      SMARTLIST_FOREACH(auth_created_clients, rend_authorized_client_t *, ac, {
+        char *encoded = rend_auth_encode_cookie(ac->descriptor_cookie,
+                                                auth_type);
+        tor_assert(encoded);
+        connection_printf_to_buf(conn, "250-ClientAuth=%s:%s\r\n",
+                                 ac->client_name, encoded);
+        memwipe(encoded, 0, strlen(encoded));
+        tor_free(encoded);
+      });
+    }
+
+    connection_printf_to_buf(conn, "250 OK\r\n");
     break;
   }
   case RSAE_BADPRIVKEY:
@@ -3900,6 +3957,9 @@ handle_control_add_onion(control_connection_t *conn,
   case RSAE_BADVIRTPORT:
     connection_printf_to_buf(conn, "512 Invalid VIRTPORT/TARGET\r\n");
     break;
+  case RSAE_BADAUTH:
+    connection_printf_to_buf(conn, "512 Invalid client authorization\r\n");
+    break;
   case RSAE_INTERNAL: /* FALLSTHROUGH */
   default:
     connection_printf_to_buf(conn, "551 Failed to add Onion Service\r\n");
@@ -3916,6 +3976,16 @@ handle_control_add_onion(control_connection_t *conn,
     smartlist_free(port_cfgs);
   }
 
+  if (auth_clients) {
+    SMARTLIST_FOREACH(auth_clients, rend_authorized_client_t *, ac,
+                      rend_authorized_client_free(ac));
+    smartlist_free(auth_clients);
+  }
+  if (auth_created_clients) {
+    // Do not free entries; they are the same as auth_clients
+    smartlist_free(auth_created_clients);
+  }
+
   SMARTLIST_FOREACH(args, char *, cp, {
     memwipe(cp, 0, strlen(cp));
     tor_free(cp);
@@ -4024,6 +4094,65 @@ add_onion_helper_keyarg(const char *arg, int discard_pk,
   return pk;
 }
 
+/** Helper function to handle parsing a ClientAuth argument to the
+ * ADD_ONION command.  Return a new rend_authorized_client_t, or NULL
+ * and an optional control protocol error message on failure.  The
+ * caller is responsible for freeing the returned auth_client and err_msg.
+ *
+ * If 'created' is specified, it will be set to 1 when a new cookie has
+ * been generated.
+ */
+STATIC rend_authorized_client_t *
+add_onion_helper_clientauth(const char *arg, int *created, char **err_msg)
+{
+  int ok = 0;
+
+  tor_assert(arg);
+  tor_assert(created);
+  tor_assert(err_msg);
+  *err_msg = NULL;
+
+  smartlist_t *auth_args = smartlist_new();
+  rend_authorized_client_t *client =
+    tor_malloc_zero(sizeof(rend_authorized_client_t));
+  smartlist_split_string(auth_args, arg, ":", 0, 0);
+  if (smartlist_len(auth_args) < 1 || smartlist_len(auth_args) > 2) {
+    *err_msg = tor_strdup("512 Invalid ClientAuth syntax\r\n");
+    goto err;
+  }
+  client->client_name = tor_strdup(smartlist_get(auth_args, 0));
+  if (smartlist_len(auth_args) == 2) {
+    char *decode_err_msg = NULL;
+    if (rend_auth_decode_cookie(smartlist_get(auth_args, 1),
+                                client->descriptor_cookie,
+                                NULL, &decode_err_msg) < 0) {
+      tor_assert(decode_err_msg);
+      tor_asprintf(err_msg, "512 %s\r\n", decode_err_msg);
+      tor_free(decode_err_msg);
+      goto err;
+    }
+    *created = 0;
+  } else {
+    crypto_rand((char *) client->descriptor_cookie, REND_DESC_COOKIE_LEN);
+    *created = 1;
+  }
+
+  if (!rend_valid_client_name(client->client_name)) {
+    *err_msg = tor_strdup("512 Invalid name in ClientAuth\r\n");
+    goto err;
+  }
+
+  ok = 1;
+ err:
+  SMARTLIST_FOREACH(auth_args, char *, arg, tor_free(arg));
+  smartlist_free(auth_args);
+  if (!ok) {
+    rend_authorized_client_free(client);
+    client = NULL;
+  }
+  return client;
+}
+
 /** Called when we get a DEL_ONION command; parse the body, and remove
  * the existing ephemeral Onion Service. */
 static int

+ 2 - 0
src/or/control.h

@@ -253,6 +253,8 @@ STATIC crypto_pk_t *add_onion_helper_keyarg(const char *arg, int discard_pk,
                                             const char **key_new_alg_out,
                                             char **key_new_blob_out,
                                             char **err_msg_out);
+STATIC rend_authorized_client_t *
+add_onion_helper_clientauth(const char *arg, int *created, char **err_msg_out);
 #endif
 
 #endif

+ 51 - 0
src/test/test_controller.c

@@ -154,10 +154,61 @@ test_rend_service_parse_port_config(void *arg)
   tor_free(err_msg);
 }
 
+static void
+test_add_onion_helper_clientauth(void *arg)
+{
+  rend_authorized_client_t *client = NULL;
+  char *err_msg = NULL;
+  int created = 0;
+
+  (void)arg;
+
+  /* Test "ClientName" only. */
+  client = add_onion_helper_clientauth("alice", &created, &err_msg);
+  tt_assert(client);
+  tt_assert(created);
+  tt_assert(!err_msg);
+  rend_authorized_client_free(client);
+
+  /* Test "ClientName:Blob" */
+  client = add_onion_helper_clientauth("alice:475hGBHPlq7Mc0cRZitK/B",
+                                       &created, &err_msg);
+  tt_assert(client);
+  tt_assert(!created);
+  tt_assert(!err_msg);
+  rend_authorized_client_free(client);
+
+  /* Test invalid client names */
+  client = add_onion_helper_clientauth("no*asterisks*allowed", &created,
+                                       &err_msg);
+  tt_assert(!client);
+  tt_assert(err_msg);
+  tor_free(err_msg);
+
+  /* Test invalid auth cookie */
+  client = add_onion_helper_clientauth("alice:12345", &created, &err_msg);
+  tt_assert(!client);
+  tt_assert(err_msg);
+  tor_free(err_msg);
+
+  /* Test invalid syntax */
+  client = add_onion_helper_clientauth(":475hGBHPlq7Mc0cRZitK/B", &created,
+                                       &err_msg);
+  tt_assert(!client);
+  tt_assert(err_msg);
+  tor_free(err_msg);
+
+ done:
+  rend_authorized_client_free(client);
+  tor_free(err_msg);
+}
+
 struct testcase_t controller_tests[] = {
   { "add_onion_helper_keyarg", test_add_onion_helper_keyarg, 0, NULL, NULL },
   { "rend_service_parse_port_config", test_rend_service_parse_port_config, 0,
     NULL, NULL },
+  { "add_onion_helper_clientauth", test_add_onion_helper_clientauth, 0, NULL,
+    NULL },
   END_OF_TESTCASES
 };