Loading changes/feature6411 0 → 100644 +7 −0 Original line number Diff line number Diff line o Major features (controller): - Add the ADD_ONION and DEL_ONION commands that allows the creation and management of hidden services via the controller. Closes ticket 6411. - New "GETINFO onions/current" and "GETINFO onions/detached" to get information about hidden services created via the controller. Part of ticket 6411. src/common/crypto.c +72 −0 Original line number Diff line number Diff line Loading @@ -1397,6 +1397,78 @@ crypto_pk_get_hashed_fingerprint(crypto_pk_t *pk, char *fp_out) return 0; } /** Given a crypto_pk_t <b>pk</b>, allocate a new buffer containing the * Base64 encoding of the DER representation of the private key as a NUL * terminated string, and return it via <b>priv_out</b>. Return 0 on * sucess, -1 on failure. * * It is the caller's responsibility to sanitize and free the resulting buffer. */ int crypto_pk_base64_encode(const crypto_pk_t *pk, char **priv_out) { unsigned char *der = NULL; int der_len; int ret = -1; *priv_out = NULL; der_len = i2d_RSAPrivateKey(pk->key, &der); if (der_len < 0 || der == NULL) return ret; size_t priv_len = base64_encode_size(der_len, 0) + 1; char *priv = tor_malloc_zero(priv_len); if (base64_encode(priv, priv_len, (char *)der, der_len, 0) >= 0) { *priv_out = priv; ret = 0; } else { tor_free(priv); } memwipe(der, 0, der_len); OPENSSL_free(der); return ret; } /** Given a string containing the Base64 encoded DER representation of the * private key <b>str</b>, decode and return the result on success, or NULL * on failure. */ crypto_pk_t * crypto_pk_base64_decode(const char *str, size_t len) { crypto_pk_t *pk = NULL; char *der = tor_malloc_zero(len + 1); int der_len = base64_decode(der, len, str, len); if (der_len <= 0) { log_warn(LD_CRYPTO, "Stored RSA private key seems corrupted (base64)."); goto out; } const unsigned char *dp = (unsigned char*)der; /* Shut the compiler up. */ RSA *rsa = d2i_RSAPrivateKey(NULL, &dp, der_len); if (!rsa) { crypto_log_errors(LOG_WARN, "decoding private key"); goto out; } pk = crypto_new_pk_from_rsa_(rsa); /* Make sure it's valid. */ if (crypto_pk_check_key(pk) <= 0) { crypto_pk_free(pk); pk = NULL; goto out; } out: memwipe(der, 0, len + 1); tor_free(der); return pk; } /* symmetric crypto */ /** Return a pointer to the key set for the cipher in <b>env</b>. Loading src/common/crypto.h +3 −0 Original line number Diff line number Diff line Loading @@ -184,6 +184,9 @@ int crypto_pk_get_all_digests(crypto_pk_t *pk, digests_t *digests_out); int crypto_pk_get_fingerprint(crypto_pk_t *pk, char *fp_out,int add_space); int crypto_pk_get_hashed_fingerprint(crypto_pk_t *pk, char *fp_out); int crypto_pk_base64_encode(const crypto_pk_t *pk, char **priv_out); crypto_pk_t *crypto_pk_base64_decode(const char *str, size_t len); /* symmetric crypto */ const char *crypto_cipher_get_key(crypto_cipher_t *env); Loading src/or/connection.c +7 −0 Original line number Diff line number Diff line Loading @@ -586,6 +586,13 @@ connection_free_(connection_t *conn) control_connection_t *control_conn = TO_CONTROL_CONN(conn); tor_free(control_conn->safecookie_client_hash); tor_free(control_conn->incoming_cmd); if (control_conn->ephemeral_onion_services) { SMARTLIST_FOREACH(control_conn->ephemeral_onion_services, char *, cp, { memwipe(cp, 0, strlen(cp)); tor_free(cp); }); smartlist_free(control_conn->ephemeral_onion_services); } } /* Probably already freed by connection_free. */ Loading src/or/control.c +405 −0 Original line number Diff line number Diff line Loading @@ -36,6 +36,8 @@ #include "networkstatus.h" #include "nodelist.h" #include "policies.h" #include "rendcommon.h" #include "rendservice.h" #include "reasons.h" #include "rendclient.h" #include "rendcommon.h" Loading Loading @@ -96,6 +98,11 @@ static uint8_t *authentication_cookie = NULL; "Tor safe cookie authentication controller-to-server hash" #define SAFECOOKIE_SERVER_NONCE_LEN DIGEST256_LEN /** The list of onion services that have been added via ADD_ONION that do not * belong to any particular control connection. */ static smartlist_t *detached_onion_services = NULL; /** A sufficiently large size to record the last bootstrap phase string. */ #define BOOTSTRAP_MSG_LEN 1024 Loading Loading @@ -163,6 +170,10 @@ static int handle_control_usefeature(control_connection_t *conn, const char *body); static int handle_control_hsfetch(control_connection_t *conn, uint32_t len, const char *body); static int handle_control_add_onion(control_connection_t *conn, uint32_t len, const char *body); static int handle_control_del_onion(control_connection_t *conn, uint32_t len, const char *body); static int write_stream_target_to_buf(entry_connection_t *conn, char *buf, size_t len); static void orconn_target_get_name(char *buf, size_t len, Loading Loading @@ -2170,6 +2181,31 @@ getinfo_helper_events(control_connection_t *control_conn, return 0; } /** Implementation helper for GETINFO: knows how to enumerate hidden services * created via the control port. */ static int getinfo_helper_onions(control_connection_t *control_conn, const char *question, char **answer, const char **errmsg) { smartlist_t *onion_list = NULL; if (!strcmp(question, "onions/current")) { onion_list = control_conn->ephemeral_onion_services; } else if (!strcmp(question, "onions/detached")) { onion_list = detached_onion_services; } else { return 0; } if (!onion_list || smartlist_len(onion_list) == 0) { *errmsg = "No onion services of the specified type."; return -1; } *answer = smartlist_join_strings(onion_list, "\r\n", 0, NULL); return 0; } /** Callback function for GETINFO: on a given control connection, try to * answer the question <b>q</b> and store the newly-allocated answer in * *<b>a</b>. If an internal error occurs, return -1 and optionally set Loading Loading @@ -2306,6 +2342,10 @@ static const getinfo_item_t getinfo_items[] = { ITEM("exit-policy/ipv4", policies, "IPv4 parts of exit policy"), ITEM("exit-policy/ipv6", policies, "IPv6 parts of exit policy"), PREFIX("ip-to-country/", geoip, "Perform a GEOIP lookup"), ITEM("onions/current", onions, "Onion services owned by the current control connection."), ITEM("onions/detached", onions, "Onion services detached from the control connection."), { NULL, NULL, NULL, 0 } }; Loading Loading @@ -3388,6 +3428,348 @@ exit: return 0; } /** Called when we get a ADD_ONION command; parse the body, and set up * the new ephemeral Onion Service. */ static int handle_control_add_onion(control_connection_t *conn, uint32_t len, const char *body) { smartlist_t *args; size_t arg_len; (void) len; /* body is nul-terminated; it's safe to ignore the length */ args = getargs_helper("ADD_ONION", conn, body, 2, -1); if (!args) return 0; arg_len = smartlist_len(args); /* Parse all of the arguments that do not involve handling cryptographic * material first, since there's no reason to touch that at all if any of * the other arguments are malformed. */ smartlist_t *port_cfgs = smartlist_new(); int discard_pk = 0; int detach = 0; for (size_t i = 1; i < arg_len; i++) { static const char *port_prefix = "Port="; static const char *flags_prefix = "Flags="; const char *arg = smartlist_get(args, i); if (!strcasecmpstart(arg, port_prefix)) { /* "Port=VIRTPORT[,TARGET]". */ const char *port_str = arg + strlen(port_prefix); rend_service_port_config_t *cfg = rend_service_parse_port_config(port_str, ",", NULL); if (!cfg) { connection_printf_to_buf(conn, "512 Invalid VIRTPORT/TARGET\r\n"); goto out; } smartlist_add(port_cfgs, cfg); } else if (!strcasecmpstart(arg, flags_prefix)) { /* "Flags=Flag[,Flag]", where Flag can be: * * 'DiscardPK' - If tor generates the keypair, do not include it in * the response. * * 'Detach' - Do not tie this onion service to any particular control * connection. */ static const char *discard_flag = "DiscardPK"; static const char *detach_flag = "Detach"; smartlist_t *flags = smartlist_new(); int bad = 0; smartlist_split_string(flags, arg + strlen(flags_prefix), ",", SPLIT_IGNORE_BLANK, 0); if (smartlist_len(flags) < 1) { connection_printf_to_buf(conn, "512 Invalid 'Flags' argument\r\n"); bad = 1; } SMARTLIST_FOREACH_BEGIN(flags, const char *, flag) { if (!strcasecmp(flag, discard_flag)) { discard_pk = 1; } else if (!strcasecmp(flag, detach_flag)) { detach = 1; } else { connection_printf_to_buf(conn, "512 Invalid 'Flags' argument: %s\r\n", escaped(flag)); bad = 1; break; } } SMARTLIST_FOREACH_END(flag); SMARTLIST_FOREACH(flags, char *, cp, tor_free(cp)); smartlist_free(flags); if (bad) goto out; } else { connection_printf_to_buf(conn, "513 Invalid argument\r\n"); goto out; } } if (smartlist_len(port_cfgs) == 0) { connection_printf_to_buf(conn, "512 Missing 'Port' argument\r\n"); goto out; } /* Parse the "keytype:keyblob" argument. */ crypto_pk_t *pk = NULL; const char *key_new_alg = NULL; char *key_new_blob = NULL; char *err_msg = NULL; pk = add_onion_helper_keyarg(smartlist_get(args, 0), discard_pk, &key_new_alg, &key_new_blob, &err_msg); if (!pk) { if (err_msg) { connection_write_str_to_buf(err_msg, conn); tor_free(err_msg); } goto out; } tor_assert(!err_msg); /* Create the HS, using private key pk, and port config port_cfg. * rend_service_add_ephemeral() will take ownership of pk and port_cfg, * regardless of success/failure. */ char *service_id = NULL; int ret = rend_service_add_ephemeral(pk, port_cfgs, &service_id); port_cfgs = NULL; /* port_cfgs is now owned by the rendservice code. */ 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(); smartlist_add(detached_onion_services, service_id); } else { if (!conn->ephemeral_onion_services) conn->ephemeral_onion_services = smartlist_new(); smartlist_add(conn->ephemeral_onion_services, service_id); } connection_write_str_to_buf(buf, conn); memwipe(buf, 0, strlen(buf)); tor_free(buf); break; } case RSAE_BADPRIVKEY: connection_printf_to_buf(conn, "551 Failed to generate onion address\r\n"); break; case RSAE_ADDREXISTS: connection_printf_to_buf(conn, "550 Onion address collision\r\n"); break; case RSAE_BADVIRTPORT: connection_printf_to_buf(conn, "512 Invalid VIRTPORT/TARGET\r\n"); break; case RSAE_INTERNAL: /* FALLSTHROUGH */ default: connection_printf_to_buf(conn, "551 Failed to add Onion Service\r\n"); } if (key_new_blob) { memwipe(key_new_blob, 0, strlen(key_new_blob)); tor_free(key_new_blob); } out: if (port_cfgs) { SMARTLIST_FOREACH(port_cfgs, rend_service_port_config_t*, p, rend_service_port_config_free(p)); smartlist_free(port_cfgs); } SMARTLIST_FOREACH(args, char *, cp, { memwipe(cp, 0, strlen(cp)); tor_free(cp); }); smartlist_free(args); return 0; } /** Helper function to handle parsing the KeyType:KeyBlob argument to the * ADD_ONION command. Return a new crypto_pk_t and if a new key was generated * and the private key not discarded, the algorithm and serialized private key, * or NULL and an optional control protocol error message on failure. The * caller is responsible for freeing the returned key_new_blob and err_msg. * * Note: The error messages returned are deliberately vague to avoid echoing * key material. */ 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) { smartlist_t *key_args = smartlist_new(); crypto_pk_t *pk = NULL; const char *key_new_alg = NULL; char *key_new_blob = NULL; char *err_msg = NULL; int ok = 0; smartlist_split_string(key_args, arg, ":", SPLIT_IGNORE_BLANK, 0); if (smartlist_len(key_args) != 2) { err_msg = tor_strdup("512 Invalid key type/blob\r\n"); goto err; } /* The format is "KeyType:KeyBlob". */ static const char *key_type_new = "NEW"; static const char *key_type_best = "BEST"; static const char *key_type_rsa1024 = "RSA1024"; const char *key_type = smartlist_get(key_args, 0); const char *key_blob = smartlist_get(key_args, 1); if (!strcasecmp(key_type_rsa1024, key_type)) { /* "RSA:<Base64 Blob>" - Loading a pre-existing RSA1024 key. */ pk = crypto_pk_base64_decode(key_blob, strlen(key_blob)); if (!pk) { err_msg = tor_strdup("512 Failed to decode RSA key\r\n"); goto err; } if (crypto_pk_num_bits(pk) != PK_BYTES*8) { err_msg = tor_strdup("512 Invalid RSA key size\r\n"); goto err; } } else if (!strcasecmp(key_type_new, key_type)) { /* "NEW:<Algorithm>" - Generating a new key, blob as algorithm. */ if (!strcasecmp(key_type_rsa1024, key_blob) || !strcasecmp(key_type_best, key_blob)) { /* "RSA1024", RSA 1024 bit, also currently "BEST" by default. */ pk = crypto_pk_new(); if (crypto_pk_generate_key(pk)) { tor_asprintf(&err_msg, "551 Failed to generate %s key\r\n", key_type_rsa1024); goto err; } if (!discard_pk) { if (crypto_pk_base64_encode(pk, &key_new_blob)) { tor_asprintf(&err_msg, "551 Failed to encode %s key\r\n", key_type_rsa1024); goto err; } key_new_alg = key_type_rsa1024; } } else { err_msg = tor_strdup("513 Invalid key type\r\n"); goto err; } } else { err_msg = tor_strdup("513 Invalid key type\r\n"); goto err; } /* Succeded in loading or generating a private key. */ tor_assert(pk); ok = 1; err: SMARTLIST_FOREACH(key_args, char *, cp, { memwipe(cp, 0, strlen(cp)); tor_free(cp); }); if (!ok) { crypto_pk_free(pk); pk = NULL; } if (err_msg_out) *err_msg_out = err_msg; *key_new_alg_out = key_new_alg; *key_new_blob_out = key_new_blob; return pk; } /** Called when we get a DEL_ONION command; parse the body, and remove * the existing ephemeral Onion Service. */ static int handle_control_del_onion(control_connection_t *conn, uint32_t len, const char *body) { smartlist_t *args; (void) len; /* body is nul-terminated; it's safe to ignore the length */ args = getargs_helper("DEL_ONION", conn, body, 1, 1); if (!args) return 0; const char *service_id = smartlist_get(args, 0); if (!rend_valid_service_id(service_id)) { connection_printf_to_buf(conn, "512 Malformed Onion Service id\r\n"); goto out; } /* Determine if the onion service belongs to this particular control * connection, or if it is in the global list of detached services. If it * is in neither, either the service ID is invalid in some way, or it * explicitly belongs to a different control connection, and an error * should be returned. */ smartlist_t *services[2] = { conn->ephemeral_onion_services, detached_onion_services }; smartlist_t *onion_services = NULL; int idx = -1; for (size_t i = 0; i < ARRAY_LENGTH(services); i++) { idx = smartlist_string_pos(services[i], service_id); if (idx != -1) { onion_services = services[i]; break; } } if (onion_services == NULL) { connection_printf_to_buf(conn, "552 Unknown Onion Service id\r\n"); } else { int ret = rend_service_del_ephemeral(service_id); if (ret) { /* This should *NEVER* fail, since the service is on either the * per-control connection list, or the global one. */ log_warn(LD_BUG, "Failed to remove Onion Service %s.", escaped(service_id)); tor_fragile_assert(); } /* Remove/scrub the service_id from the appropriate list. */ char *cp = smartlist_get(onion_services, idx); smartlist_del(onion_services, idx); memwipe(cp, 0, strlen(cp)); tor_free(cp); send_control_done(conn); } out: SMARTLIST_FOREACH(args, char *, cp, { memwipe(cp, 0, strlen(cp)); tor_free(cp); }); smartlist_free(args); return 0; } /** Called when <b>conn</b> has no more bytes left on its outbuf. */ int connection_control_finished_flushing(control_connection_t *conn) Loading Loading @@ -3434,6 +3816,15 @@ connection_control_closed(control_connection_t *conn) conn->event_mask = 0; control_update_global_event_mask(); /* Close all ephemeral Onion Services if any. * The list and it's contents are scrubbed/freed in connection_free_. */ if (conn->ephemeral_onion_services) { SMARTLIST_FOREACH(conn->ephemeral_onion_services, char *, cp, { rend_service_del_ephemeral(cp); }); } if (conn->is_owning_control_connection) { lost_owning_controller("connection", "closed"); } Loading Loading @@ -3688,6 +4079,16 @@ connection_control_process_inbuf(control_connection_t *conn) } else if (!strcasecmp(conn->incoming_cmd, "HSFETCH")) { if (handle_control_hsfetch(conn, cmd_data_len, args)) return -1; } else if (!strcasecmp(conn->incoming_cmd, "ADD_ONION")) { int ret = handle_control_add_onion(conn, cmd_data_len, args); memwipe(args, 0, cmd_data_len); /* Scrub the private key. */ if (ret) return -1; } else if (!strcasecmp(conn->incoming_cmd, "DEL_ONION")) { int ret = handle_control_del_onion(conn, cmd_data_len, args); memwipe(args, 0, cmd_data_len); /* Scrub the service id/pk. */ if (ret) return -1; } else { connection_printf_to_buf(conn, "510 Unrecognized command \"%s\"\r\n", conn->incoming_cmd); Loading Loading @@ -5520,6 +5921,10 @@ control_free_all(void) { if (authentication_cookie) /* Free the auth cookie */ tor_free(authentication_cookie); if (detached_onion_services) { /* Free the detached onion services */ SMARTLIST_FOREACH(detached_onion_services, char *, cp, tor_free(cp)); smartlist_free(detached_onion_services); } } #ifdef TOR_UNIT_TESTS Loading Loading
changes/feature6411 0 → 100644 +7 −0 Original line number Diff line number Diff line o Major features (controller): - Add the ADD_ONION and DEL_ONION commands that allows the creation and management of hidden services via the controller. Closes ticket 6411. - New "GETINFO onions/current" and "GETINFO onions/detached" to get information about hidden services created via the controller. Part of ticket 6411.
src/common/crypto.c +72 −0 Original line number Diff line number Diff line Loading @@ -1397,6 +1397,78 @@ crypto_pk_get_hashed_fingerprint(crypto_pk_t *pk, char *fp_out) return 0; } /** Given a crypto_pk_t <b>pk</b>, allocate a new buffer containing the * Base64 encoding of the DER representation of the private key as a NUL * terminated string, and return it via <b>priv_out</b>. Return 0 on * sucess, -1 on failure. * * It is the caller's responsibility to sanitize and free the resulting buffer. */ int crypto_pk_base64_encode(const crypto_pk_t *pk, char **priv_out) { unsigned char *der = NULL; int der_len; int ret = -1; *priv_out = NULL; der_len = i2d_RSAPrivateKey(pk->key, &der); if (der_len < 0 || der == NULL) return ret; size_t priv_len = base64_encode_size(der_len, 0) + 1; char *priv = tor_malloc_zero(priv_len); if (base64_encode(priv, priv_len, (char *)der, der_len, 0) >= 0) { *priv_out = priv; ret = 0; } else { tor_free(priv); } memwipe(der, 0, der_len); OPENSSL_free(der); return ret; } /** Given a string containing the Base64 encoded DER representation of the * private key <b>str</b>, decode and return the result on success, or NULL * on failure. */ crypto_pk_t * crypto_pk_base64_decode(const char *str, size_t len) { crypto_pk_t *pk = NULL; char *der = tor_malloc_zero(len + 1); int der_len = base64_decode(der, len, str, len); if (der_len <= 0) { log_warn(LD_CRYPTO, "Stored RSA private key seems corrupted (base64)."); goto out; } const unsigned char *dp = (unsigned char*)der; /* Shut the compiler up. */ RSA *rsa = d2i_RSAPrivateKey(NULL, &dp, der_len); if (!rsa) { crypto_log_errors(LOG_WARN, "decoding private key"); goto out; } pk = crypto_new_pk_from_rsa_(rsa); /* Make sure it's valid. */ if (crypto_pk_check_key(pk) <= 0) { crypto_pk_free(pk); pk = NULL; goto out; } out: memwipe(der, 0, len + 1); tor_free(der); return pk; } /* symmetric crypto */ /** Return a pointer to the key set for the cipher in <b>env</b>. Loading
src/common/crypto.h +3 −0 Original line number Diff line number Diff line Loading @@ -184,6 +184,9 @@ int crypto_pk_get_all_digests(crypto_pk_t *pk, digests_t *digests_out); int crypto_pk_get_fingerprint(crypto_pk_t *pk, char *fp_out,int add_space); int crypto_pk_get_hashed_fingerprint(crypto_pk_t *pk, char *fp_out); int crypto_pk_base64_encode(const crypto_pk_t *pk, char **priv_out); crypto_pk_t *crypto_pk_base64_decode(const char *str, size_t len); /* symmetric crypto */ const char *crypto_cipher_get_key(crypto_cipher_t *env); Loading
src/or/connection.c +7 −0 Original line number Diff line number Diff line Loading @@ -586,6 +586,13 @@ connection_free_(connection_t *conn) control_connection_t *control_conn = TO_CONTROL_CONN(conn); tor_free(control_conn->safecookie_client_hash); tor_free(control_conn->incoming_cmd); if (control_conn->ephemeral_onion_services) { SMARTLIST_FOREACH(control_conn->ephemeral_onion_services, char *, cp, { memwipe(cp, 0, strlen(cp)); tor_free(cp); }); smartlist_free(control_conn->ephemeral_onion_services); } } /* Probably already freed by connection_free. */ Loading
src/or/control.c +405 −0 Original line number Diff line number Diff line Loading @@ -36,6 +36,8 @@ #include "networkstatus.h" #include "nodelist.h" #include "policies.h" #include "rendcommon.h" #include "rendservice.h" #include "reasons.h" #include "rendclient.h" #include "rendcommon.h" Loading Loading @@ -96,6 +98,11 @@ static uint8_t *authentication_cookie = NULL; "Tor safe cookie authentication controller-to-server hash" #define SAFECOOKIE_SERVER_NONCE_LEN DIGEST256_LEN /** The list of onion services that have been added via ADD_ONION that do not * belong to any particular control connection. */ static smartlist_t *detached_onion_services = NULL; /** A sufficiently large size to record the last bootstrap phase string. */ #define BOOTSTRAP_MSG_LEN 1024 Loading Loading @@ -163,6 +170,10 @@ static int handle_control_usefeature(control_connection_t *conn, const char *body); static int handle_control_hsfetch(control_connection_t *conn, uint32_t len, const char *body); static int handle_control_add_onion(control_connection_t *conn, uint32_t len, const char *body); static int handle_control_del_onion(control_connection_t *conn, uint32_t len, const char *body); static int write_stream_target_to_buf(entry_connection_t *conn, char *buf, size_t len); static void orconn_target_get_name(char *buf, size_t len, Loading Loading @@ -2170,6 +2181,31 @@ getinfo_helper_events(control_connection_t *control_conn, return 0; } /** Implementation helper for GETINFO: knows how to enumerate hidden services * created via the control port. */ static int getinfo_helper_onions(control_connection_t *control_conn, const char *question, char **answer, const char **errmsg) { smartlist_t *onion_list = NULL; if (!strcmp(question, "onions/current")) { onion_list = control_conn->ephemeral_onion_services; } else if (!strcmp(question, "onions/detached")) { onion_list = detached_onion_services; } else { return 0; } if (!onion_list || smartlist_len(onion_list) == 0) { *errmsg = "No onion services of the specified type."; return -1; } *answer = smartlist_join_strings(onion_list, "\r\n", 0, NULL); return 0; } /** Callback function for GETINFO: on a given control connection, try to * answer the question <b>q</b> and store the newly-allocated answer in * *<b>a</b>. If an internal error occurs, return -1 and optionally set Loading Loading @@ -2306,6 +2342,10 @@ static const getinfo_item_t getinfo_items[] = { ITEM("exit-policy/ipv4", policies, "IPv4 parts of exit policy"), ITEM("exit-policy/ipv6", policies, "IPv6 parts of exit policy"), PREFIX("ip-to-country/", geoip, "Perform a GEOIP lookup"), ITEM("onions/current", onions, "Onion services owned by the current control connection."), ITEM("onions/detached", onions, "Onion services detached from the control connection."), { NULL, NULL, NULL, 0 } }; Loading Loading @@ -3388,6 +3428,348 @@ exit: return 0; } /** Called when we get a ADD_ONION command; parse the body, and set up * the new ephemeral Onion Service. */ static int handle_control_add_onion(control_connection_t *conn, uint32_t len, const char *body) { smartlist_t *args; size_t arg_len; (void) len; /* body is nul-terminated; it's safe to ignore the length */ args = getargs_helper("ADD_ONION", conn, body, 2, -1); if (!args) return 0; arg_len = smartlist_len(args); /* Parse all of the arguments that do not involve handling cryptographic * material first, since there's no reason to touch that at all if any of * the other arguments are malformed. */ smartlist_t *port_cfgs = smartlist_new(); int discard_pk = 0; int detach = 0; for (size_t i = 1; i < arg_len; i++) { static const char *port_prefix = "Port="; static const char *flags_prefix = "Flags="; const char *arg = smartlist_get(args, i); if (!strcasecmpstart(arg, port_prefix)) { /* "Port=VIRTPORT[,TARGET]". */ const char *port_str = arg + strlen(port_prefix); rend_service_port_config_t *cfg = rend_service_parse_port_config(port_str, ",", NULL); if (!cfg) { connection_printf_to_buf(conn, "512 Invalid VIRTPORT/TARGET\r\n"); goto out; } smartlist_add(port_cfgs, cfg); } else if (!strcasecmpstart(arg, flags_prefix)) { /* "Flags=Flag[,Flag]", where Flag can be: * * 'DiscardPK' - If tor generates the keypair, do not include it in * the response. * * 'Detach' - Do not tie this onion service to any particular control * connection. */ static const char *discard_flag = "DiscardPK"; static const char *detach_flag = "Detach"; smartlist_t *flags = smartlist_new(); int bad = 0; smartlist_split_string(flags, arg + strlen(flags_prefix), ",", SPLIT_IGNORE_BLANK, 0); if (smartlist_len(flags) < 1) { connection_printf_to_buf(conn, "512 Invalid 'Flags' argument\r\n"); bad = 1; } SMARTLIST_FOREACH_BEGIN(flags, const char *, flag) { if (!strcasecmp(flag, discard_flag)) { discard_pk = 1; } else if (!strcasecmp(flag, detach_flag)) { detach = 1; } else { connection_printf_to_buf(conn, "512 Invalid 'Flags' argument: %s\r\n", escaped(flag)); bad = 1; break; } } SMARTLIST_FOREACH_END(flag); SMARTLIST_FOREACH(flags, char *, cp, tor_free(cp)); smartlist_free(flags); if (bad) goto out; } else { connection_printf_to_buf(conn, "513 Invalid argument\r\n"); goto out; } } if (smartlist_len(port_cfgs) == 0) { connection_printf_to_buf(conn, "512 Missing 'Port' argument\r\n"); goto out; } /* Parse the "keytype:keyblob" argument. */ crypto_pk_t *pk = NULL; const char *key_new_alg = NULL; char *key_new_blob = NULL; char *err_msg = NULL; pk = add_onion_helper_keyarg(smartlist_get(args, 0), discard_pk, &key_new_alg, &key_new_blob, &err_msg); if (!pk) { if (err_msg) { connection_write_str_to_buf(err_msg, conn); tor_free(err_msg); } goto out; } tor_assert(!err_msg); /* Create the HS, using private key pk, and port config port_cfg. * rend_service_add_ephemeral() will take ownership of pk and port_cfg, * regardless of success/failure. */ char *service_id = NULL; int ret = rend_service_add_ephemeral(pk, port_cfgs, &service_id); port_cfgs = NULL; /* port_cfgs is now owned by the rendservice code. */ 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(); smartlist_add(detached_onion_services, service_id); } else { if (!conn->ephemeral_onion_services) conn->ephemeral_onion_services = smartlist_new(); smartlist_add(conn->ephemeral_onion_services, service_id); } connection_write_str_to_buf(buf, conn); memwipe(buf, 0, strlen(buf)); tor_free(buf); break; } case RSAE_BADPRIVKEY: connection_printf_to_buf(conn, "551 Failed to generate onion address\r\n"); break; case RSAE_ADDREXISTS: connection_printf_to_buf(conn, "550 Onion address collision\r\n"); break; case RSAE_BADVIRTPORT: connection_printf_to_buf(conn, "512 Invalid VIRTPORT/TARGET\r\n"); break; case RSAE_INTERNAL: /* FALLSTHROUGH */ default: connection_printf_to_buf(conn, "551 Failed to add Onion Service\r\n"); } if (key_new_blob) { memwipe(key_new_blob, 0, strlen(key_new_blob)); tor_free(key_new_blob); } out: if (port_cfgs) { SMARTLIST_FOREACH(port_cfgs, rend_service_port_config_t*, p, rend_service_port_config_free(p)); smartlist_free(port_cfgs); } SMARTLIST_FOREACH(args, char *, cp, { memwipe(cp, 0, strlen(cp)); tor_free(cp); }); smartlist_free(args); return 0; } /** Helper function to handle parsing the KeyType:KeyBlob argument to the * ADD_ONION command. Return a new crypto_pk_t and if a new key was generated * and the private key not discarded, the algorithm and serialized private key, * or NULL and an optional control protocol error message on failure. The * caller is responsible for freeing the returned key_new_blob and err_msg. * * Note: The error messages returned are deliberately vague to avoid echoing * key material. */ 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) { smartlist_t *key_args = smartlist_new(); crypto_pk_t *pk = NULL; const char *key_new_alg = NULL; char *key_new_blob = NULL; char *err_msg = NULL; int ok = 0; smartlist_split_string(key_args, arg, ":", SPLIT_IGNORE_BLANK, 0); if (smartlist_len(key_args) != 2) { err_msg = tor_strdup("512 Invalid key type/blob\r\n"); goto err; } /* The format is "KeyType:KeyBlob". */ static const char *key_type_new = "NEW"; static const char *key_type_best = "BEST"; static const char *key_type_rsa1024 = "RSA1024"; const char *key_type = smartlist_get(key_args, 0); const char *key_blob = smartlist_get(key_args, 1); if (!strcasecmp(key_type_rsa1024, key_type)) { /* "RSA:<Base64 Blob>" - Loading a pre-existing RSA1024 key. */ pk = crypto_pk_base64_decode(key_blob, strlen(key_blob)); if (!pk) { err_msg = tor_strdup("512 Failed to decode RSA key\r\n"); goto err; } if (crypto_pk_num_bits(pk) != PK_BYTES*8) { err_msg = tor_strdup("512 Invalid RSA key size\r\n"); goto err; } } else if (!strcasecmp(key_type_new, key_type)) { /* "NEW:<Algorithm>" - Generating a new key, blob as algorithm. */ if (!strcasecmp(key_type_rsa1024, key_blob) || !strcasecmp(key_type_best, key_blob)) { /* "RSA1024", RSA 1024 bit, also currently "BEST" by default. */ pk = crypto_pk_new(); if (crypto_pk_generate_key(pk)) { tor_asprintf(&err_msg, "551 Failed to generate %s key\r\n", key_type_rsa1024); goto err; } if (!discard_pk) { if (crypto_pk_base64_encode(pk, &key_new_blob)) { tor_asprintf(&err_msg, "551 Failed to encode %s key\r\n", key_type_rsa1024); goto err; } key_new_alg = key_type_rsa1024; } } else { err_msg = tor_strdup("513 Invalid key type\r\n"); goto err; } } else { err_msg = tor_strdup("513 Invalid key type\r\n"); goto err; } /* Succeded in loading or generating a private key. */ tor_assert(pk); ok = 1; err: SMARTLIST_FOREACH(key_args, char *, cp, { memwipe(cp, 0, strlen(cp)); tor_free(cp); }); if (!ok) { crypto_pk_free(pk); pk = NULL; } if (err_msg_out) *err_msg_out = err_msg; *key_new_alg_out = key_new_alg; *key_new_blob_out = key_new_blob; return pk; } /** Called when we get a DEL_ONION command; parse the body, and remove * the existing ephemeral Onion Service. */ static int handle_control_del_onion(control_connection_t *conn, uint32_t len, const char *body) { smartlist_t *args; (void) len; /* body is nul-terminated; it's safe to ignore the length */ args = getargs_helper("DEL_ONION", conn, body, 1, 1); if (!args) return 0; const char *service_id = smartlist_get(args, 0); if (!rend_valid_service_id(service_id)) { connection_printf_to_buf(conn, "512 Malformed Onion Service id\r\n"); goto out; } /* Determine if the onion service belongs to this particular control * connection, or if it is in the global list of detached services. If it * is in neither, either the service ID is invalid in some way, or it * explicitly belongs to a different control connection, and an error * should be returned. */ smartlist_t *services[2] = { conn->ephemeral_onion_services, detached_onion_services }; smartlist_t *onion_services = NULL; int idx = -1; for (size_t i = 0; i < ARRAY_LENGTH(services); i++) { idx = smartlist_string_pos(services[i], service_id); if (idx != -1) { onion_services = services[i]; break; } } if (onion_services == NULL) { connection_printf_to_buf(conn, "552 Unknown Onion Service id\r\n"); } else { int ret = rend_service_del_ephemeral(service_id); if (ret) { /* This should *NEVER* fail, since the service is on either the * per-control connection list, or the global one. */ log_warn(LD_BUG, "Failed to remove Onion Service %s.", escaped(service_id)); tor_fragile_assert(); } /* Remove/scrub the service_id from the appropriate list. */ char *cp = smartlist_get(onion_services, idx); smartlist_del(onion_services, idx); memwipe(cp, 0, strlen(cp)); tor_free(cp); send_control_done(conn); } out: SMARTLIST_FOREACH(args, char *, cp, { memwipe(cp, 0, strlen(cp)); tor_free(cp); }); smartlist_free(args); return 0; } /** Called when <b>conn</b> has no more bytes left on its outbuf. */ int connection_control_finished_flushing(control_connection_t *conn) Loading Loading @@ -3434,6 +3816,15 @@ connection_control_closed(control_connection_t *conn) conn->event_mask = 0; control_update_global_event_mask(); /* Close all ephemeral Onion Services if any. * The list and it's contents are scrubbed/freed in connection_free_. */ if (conn->ephemeral_onion_services) { SMARTLIST_FOREACH(conn->ephemeral_onion_services, char *, cp, { rend_service_del_ephemeral(cp); }); } if (conn->is_owning_control_connection) { lost_owning_controller("connection", "closed"); } Loading Loading @@ -3688,6 +4079,16 @@ connection_control_process_inbuf(control_connection_t *conn) } else if (!strcasecmp(conn->incoming_cmd, "HSFETCH")) { if (handle_control_hsfetch(conn, cmd_data_len, args)) return -1; } else if (!strcasecmp(conn->incoming_cmd, "ADD_ONION")) { int ret = handle_control_add_onion(conn, cmd_data_len, args); memwipe(args, 0, cmd_data_len); /* Scrub the private key. */ if (ret) return -1; } else if (!strcasecmp(conn->incoming_cmd, "DEL_ONION")) { int ret = handle_control_del_onion(conn, cmd_data_len, args); memwipe(args, 0, cmd_data_len); /* Scrub the service id/pk. */ if (ret) return -1; } else { connection_printf_to_buf(conn, "510 Unrecognized command \"%s\"\r\n", conn->incoming_cmd); Loading Loading @@ -5520,6 +5921,10 @@ control_free_all(void) { if (authentication_cookie) /* Free the auth cookie */ tor_free(authentication_cookie); if (detached_onion_services) { /* Free the detached onion services */ SMARTLIST_FOREACH(detached_onion_services, char *, cp, tor_free(cp)); smartlist_free(detached_onion_services); } } #ifdef TOR_UNIT_TESTS Loading