commit dcc11674db53cc89228ec2e8d49f30bdf6c9b8a3 Author: John Brooks john.brooks@dereferenced.net Date: Mon Apr 13 21:08:31 2015 -0600
Add client auth for ADD_ONION services --- src/or/control.c | 171 +++++++++++++++++++++++++++++++++++++++------ src/or/control.h | 2 + src/test/test_controller.c | 51 ++++++++++++++ 3 files changed, 203 insertions(+), 21 deletions(-)
diff --git a/src/or/control.c b/src/or/control.c index a215eb8..de18c9e 100644 --- a/src/or/control.c +++ b/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 diff --git a/src/or/control.h b/src/or/control.h index fdf7903..9ac8995 100644 --- a/src/or/control.h +++ b/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 diff --git a/src/test/test_controller.c b/src/test/test_controller.c index b40825b..2ab1c81 100644 --- a/src/test/test_controller.c +++ b/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 };
tor-commits@lists.torproject.org