|
@@ -43,7 +43,429 @@ rend_service_descriptor_free(rend_service_descriptor_t *desc)
|
|
|
tor_free(desc);
|
|
|
}
|
|
|
|
|
|
-
|
|
|
+
|
|
|
+#define REND_SERVICE_ID_BINARY 10
|
|
|
+
|
|
|
+
|
|
|
+ * versioned hidden service descriptors. */
|
|
|
+#define REND_TIME_PERIOD_BINARY 4
|
|
|
+
|
|
|
+
|
|
|
+ * service descriptors. */
|
|
|
+#define REND_DESC_COOKIE_BINARY 16
|
|
|
+
|
|
|
+
|
|
|
+ * part of versioned hidden service descriptors. */
|
|
|
+#define REND_REPLICA_BINARY 1
|
|
|
+
|
|
|
+
|
|
|
+ * descriptors. */
|
|
|
+#define REND_SECRET_ID_PART_BASE32 32
|
|
|
+
|
|
|
+
|
|
|
+ * <b>REND_SERVICE_ID_BINARY</b> and <b>secret_id_part</b> of length
|
|
|
+ * <b>DIGEST_LEN</b>, and write it to <b>descriptor_id_out</b> of length
|
|
|
+ * <b>DIGEST_LEN</b>. */
|
|
|
+void
|
|
|
+rend_get_descriptor_id_bytes(char *descriptor_id_out,
|
|
|
+ const char *service_id,
|
|
|
+ const char *secret_id_part)
|
|
|
+{
|
|
|
+ crypto_digest_env_t *digest = crypto_new_digest_env();
|
|
|
+ crypto_digest_add_bytes(digest, service_id, REND_SERVICE_ID_BINARY);
|
|
|
+ crypto_digest_add_bytes(digest, secret_id_part, DIGEST_LEN);
|
|
|
+ crypto_digest_get_digest(digest, descriptor_id_out, DIGEST_LEN);
|
|
|
+ crypto_free_digest_env(digest);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+ * <b>REND_TIME_PERIOD_BINARY</b>, <b>descriptor_cookie</b> of length
|
|
|
+ * <b>REND_DESC_COOKIE_BINARY</b> which may also be <b>NULL</b> if no
|
|
|
+ * descriptor_cookie shall be used, and <b>replica</b>, and write it to
|
|
|
+ * <b>secret_id_part</b> of length DIGEST_LEN. */
|
|
|
+static void
|
|
|
+get_secret_id_part_bytes(char *secret_id_part, const char *time_period,
|
|
|
+ const char *descriptor_cookie, uint8_t replica)
|
|
|
+{
|
|
|
+ crypto_digest_env_t *digest = crypto_new_digest_env();
|
|
|
+ crypto_digest_add_bytes(digest, time_period, REND_TIME_PERIOD_BINARY);
|
|
|
+ if (descriptor_cookie) {
|
|
|
+ crypto_digest_add_bytes(digest, descriptor_cookie,
|
|
|
+ REND_DESC_COOKIE_BINARY);
|
|
|
+ }
|
|
|
+ crypto_digest_add_bytes(digest, (const char *)&replica, REND_REPLICA_BINARY);
|
|
|
+ crypto_digest_get_digest(digest, secret_id_part, DIGEST_LEN);
|
|
|
+ crypto_free_digest_env(digest);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+ * intended <b>deviation</b> of one or more periods, and the first byte of
|
|
|
+ * <b>service_id</b>, and write it to <b>time_period</b> of length 4. */
|
|
|
+static void
|
|
|
+get_time_period_bytes(char *time_period, time_t now, uint8_t deviation,
|
|
|
+ const char *service_id)
|
|
|
+{
|
|
|
+ uint32_t host_order =
|
|
|
+ (uint32_t)
|
|
|
+ (now + ((uint8_t) *service_id) * REND_TIME_PERIOD_V2_DESC_VALIDITY / 256)
|
|
|
+ / REND_TIME_PERIOD_V2_DESC_VALIDITY + deviation;
|
|
|
+ uint32_t network_order = htonl(host_order);
|
|
|
+ set_uint32(time_period, network_order);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+ * <b>now</b> for <b>service_id</b> will be valid. */
|
|
|
+static uint32_t
|
|
|
+get_seconds_valid(time_t now, const char *service_id)
|
|
|
+{
|
|
|
+ uint32_t result = REND_TIME_PERIOD_V2_DESC_VALIDITY -
|
|
|
+ (uint32_t)
|
|
|
+ (now + ((uint8_t) *service_id) * REND_TIME_PERIOD_V2_DESC_VALIDITY / 256)
|
|
|
+ % REND_TIME_PERIOD_V2_DESC_VALIDITY;
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+ * <b>service_id</b> and binary encoded <b>descriptor_cookie</b> of length
|
|
|
+ * 16 that may be <b>NULL</b> at time <b>now</b> for replica number
|
|
|
+ * <b>replica</b>. <b>desc_id</b> needs to have <b>DIGEST_LEN</b> bytes
|
|
|
+ * free. Return 0 for success, -1 otherwise. */
|
|
|
+int
|
|
|
+rend_compute_v2_desc_id(char *desc_id_out, const char *service_id,
|
|
|
+ const char *descriptor_cookie, time_t now,
|
|
|
+ uint8_t replica)
|
|
|
+{
|
|
|
+ char service_id_binary[REND_SERVICE_ID_BINARY];
|
|
|
+ char time_period[REND_TIME_PERIOD_BINARY];
|
|
|
+ char secret_id_part[DIGEST_LEN];
|
|
|
+ if (!service_id ||
|
|
|
+ strlen(service_id) != REND_SERVICE_ID_LEN) {
|
|
|
+ log_warn(LD_REND, "Could not compute v2 descriptor ID: "
|
|
|
+ "Illegal service ID: %s", service_id);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ if (replica >= REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS) {
|
|
|
+ log_warn(LD_REND, "Could not compute v2 descriptor ID: "
|
|
|
+ "Replica number out of range: %d", replica);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (base32_decode(service_id_binary, REND_SERVICE_ID_BINARY,
|
|
|
+ service_id, REND_SERVICE_ID_LEN) < 0) {
|
|
|
+ log_warn(LD_REND, "Could not compute v2 descriptor ID: "
|
|
|
+ "Illegal characters in service ID: %s",
|
|
|
+ service_id);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ get_time_period_bytes(time_period, now, 0, service_id_binary);
|
|
|
+
|
|
|
+ get_secret_id_part_bytes(secret_id_part, time_period, descriptor_cookie,
|
|
|
+ replica);
|
|
|
+
|
|
|
+ rend_get_descriptor_id_bytes(desc_id_out, service_id_binary, secret_id_part);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+ * with <b>descriptor_cookie</b> of length 16 that may also be <b>NULL</b>,
|
|
|
+ * write them to a newly allocated string, and write a pointer to it to
|
|
|
+ * <b>ipos_base64</b>. Return 0 for success, -1 otherwise. */
|
|
|
+static int
|
|
|
+rend_encode_v2_intro_points(char **ipos_base64,
|
|
|
+ rend_service_descriptor_t *desc,
|
|
|
+ const char *descriptor_cookie)
|
|
|
+{
|
|
|
+ size_t unenc_len;
|
|
|
+ char *unenc;
|
|
|
+ size_t unenc_written = 0;
|
|
|
+ char *enc;
|
|
|
+ int enclen;
|
|
|
+ int i;
|
|
|
+ crypto_cipher_env_t *cipher;
|
|
|
+
|
|
|
+ unenc_len = desc->n_intro_points * 1000;
|
|
|
+ unenc = tor_malloc_zero(unenc_len);
|
|
|
+ for (i = 0; i < desc->n_intro_points; i++) {
|
|
|
+ char id_base32[32 + 1];
|
|
|
+ char *onion_key;
|
|
|
+ size_t onion_key_len;
|
|
|
+ crypto_pk_env_t *intro_key;
|
|
|
+ char *service_key;
|
|
|
+ size_t service_key_len;
|
|
|
+ int res;
|
|
|
+ char hex_digest[HEX_DIGEST_LEN+2];
|
|
|
+
|
|
|
+ extend_info_t *info = desc->intro_point_extend_info[i];
|
|
|
+
|
|
|
+ base32_encode(id_base32, 32 + 1, info->identity_digest, DIGEST_LEN);
|
|
|
+
|
|
|
+ if (crypto_pk_write_public_key_to_string(info->onion_key, &onion_key,
|
|
|
+ &onion_key_len) < 0) {
|
|
|
+ log_warn(LD_REND, "Could not write onion key.");
|
|
|
+ if (onion_key) tor_free(onion_key);
|
|
|
+ tor_free(unenc);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ hex_digest[0] = '$';
|
|
|
+ base16_encode(hex_digest+1, HEX_DIGEST_LEN+1,
|
|
|
+ info->identity_digest,
|
|
|
+ DIGEST_LEN);
|
|
|
+ intro_key = strmap_get(desc->intro_keys, hex_digest);
|
|
|
+ if (!intro_key ||
|
|
|
+ crypto_pk_write_public_key_to_string(intro_key, &service_key,
|
|
|
+ &service_key_len) < 0) {
|
|
|
+ log_warn(LD_REND, "Could not write intro key.");
|
|
|
+ if (service_key) tor_free(service_key);
|
|
|
+ tor_free(onion_key);
|
|
|
+ tor_free(unenc);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ res = tor_snprintf(unenc + unenc_written, unenc_len - unenc_written,
|
|
|
+ "introduction-point %s\n"
|
|
|
+ "ip-address %s\n"
|
|
|
+ "onion-port %d\n"
|
|
|
+ "onion-key\n%s"
|
|
|
+ "service-key\n%s",
|
|
|
+ id_base32,
|
|
|
+ tor_dup_addr(info->addr),
|
|
|
+ info->port,
|
|
|
+ onion_key,
|
|
|
+ service_key);
|
|
|
+ tor_free(onion_key);
|
|
|
+ tor_free(service_key);
|
|
|
+ if (res < 0) {
|
|
|
+ log_warn(LD_REND, "Not enough space for writing introduction point "
|
|
|
+ "string.");
|
|
|
+ tor_free(unenc);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ unenc_written += res;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (unenc_len < unenc_written + 2) {
|
|
|
+ log_warn(LD_REND, "Not enough space for finalizing introduction point "
|
|
|
+ "string.");
|
|
|
+ tor_free(unenc);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ unenc[unenc_written++] = '\n';
|
|
|
+ unenc[unenc_written++] = 0;
|
|
|
+
|
|
|
+ if (descriptor_cookie) {
|
|
|
+ enc = tor_malloc_zero(unenc_written + 16);
|
|
|
+ cipher = crypto_create_init_cipher(descriptor_cookie, 1);
|
|
|
+ enclen = crypto_cipher_encrypt_with_iv(cipher, enc, unenc_written + 16,
|
|
|
+ unenc, unenc_written);
|
|
|
+ crypto_free_cipher_env(cipher);
|
|
|
+ tor_free(unenc);
|
|
|
+ if (enclen < 0) {
|
|
|
+ log_warn(LD_REND, "Could not encrypt introduction point string.");
|
|
|
+ if (enc) tor_free(enc);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ unenc = enc;
|
|
|
+ unenc_written = enclen;
|
|
|
+ }
|
|
|
+
|
|
|
+ *ipos_base64 = tor_malloc_zero(unenc_written * 2);
|
|
|
+ if (base64_encode(*ipos_base64, unenc_written * 2, unenc, unenc_written)
|
|
|
+ < 0) {
|
|
|
+ log_warn(LD_REND, "Could not encode introduction point string to "
|
|
|
+ "base64.");
|
|
|
+ tor_free(unenc);
|
|
|
+ tor_free(ipos_base64);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ tor_free(unenc);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+ * succeeds, false otherwise. */
|
|
|
+static int
|
|
|
+rend_desc_v2_is_parsable(const char *desc_str)
|
|
|
+{
|
|
|
+ rend_service_descriptor_t *test_parsed;
|
|
|
+ char test_desc_id[DIGEST_LEN];
|
|
|
+ char *test_intro_content;
|
|
|
+ size_t test_intro_size;
|
|
|
+ size_t test_encoded_size;
|
|
|
+ const char *test_next;
|
|
|
+ int res = rend_parse_v2_service_descriptor(&test_parsed, test_desc_id,
|
|
|
+ &test_intro_content,
|
|
|
+ &test_intro_size,
|
|
|
+ &test_encoded_size,
|
|
|
+ &test_next, desc_str);
|
|
|
+ tor_free(test_parsed);
|
|
|
+ tor_free(test_intro_content);
|
|
|
+ return (res >= 0);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+ * <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.), write the
|
|
|
+ * ASCII-encoded outputs to newly allocated strings and add them to the
|
|
|
+ * existing <b>desc_strs</b>, and write the descriptor IDs to newly
|
|
|
+ * allocated strings and add them to the existing <b>desc_ids</b>; return
|
|
|
+ * the number of seconds that the descriptors will be found under those
|
|
|
+ * <b>desc_ids</b> by clients, or -1 if the encoding was not successful. */
|
|
|
+int
|
|
|
+rend_encode_v2_descriptors(smartlist_t *desc_strs_out,
|
|
|
+ smartlist_t *desc_ids_out,
|
|
|
+ rend_service_descriptor_t *desc, time_t now,
|
|
|
+ const char *descriptor_cookie, uint8_t period)
|
|
|
+{
|
|
|
+ char service_id[DIGEST_LEN];
|
|
|
+ char time_period[REND_TIME_PERIOD_BINARY];
|
|
|
+ char *ipos_base64 = NULL;
|
|
|
+ int k;
|
|
|
+ uint32_t seconds_valid;
|
|
|
+ if (!desc) {
|
|
|
+ log_warn(LD_REND, "Could not encode v2 descriptor: No desc given.");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ crypto_pk_get_digest(desc->pk, service_id);
|
|
|
+
|
|
|
+ get_time_period_bytes(time_period, now, period, service_id);
|
|
|
+
|
|
|
+ seconds_valid = period * REND_TIME_PERIOD_V2_DESC_VALIDITY +
|
|
|
+ get_seconds_valid(now, service_id);
|
|
|
+
|
|
|
+ if (rend_encode_v2_intro_points(&ipos_base64, desc, descriptor_cookie) < 0) {
|
|
|
+ log_warn(LD_REND, "Encoding of introduction points did not succeed.");
|
|
|
+ if (ipos_base64) tor_free(ipos_base64);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (k = 0; k < REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS; k++) {
|
|
|
+ char secret_id_part[DIGEST_LEN];
|
|
|
+ char secret_id_part_base32[REND_SECRET_ID_PART_BASE32 + 1];
|
|
|
+ char *desc_id;
|
|
|
+ char desc_id_base32[REND_DESC_ID_V2_BASE32 + 1];
|
|
|
+ char *permanent_key;
|
|
|
+ size_t permanent_key_len;
|
|
|
+ char published[ISO_TIME_LEN+1];
|
|
|
+ int i;
|
|
|
+ char protocol_versions_string[16];
|
|
|
+ size_t protocol_versions_written;
|
|
|
+ size_t desc_len;
|
|
|
+ char *desc_str;
|
|
|
+ int result = 0;
|
|
|
+ size_t written = 0;
|
|
|
+ char desc_digest[DIGEST_LEN];
|
|
|
+
|
|
|
+ get_secret_id_part_bytes(secret_id_part, time_period, descriptor_cookie,
|
|
|
+ k);
|
|
|
+ base32_encode(secret_id_part_base32, REND_SECRET_ID_PART_BASE32 + 1,
|
|
|
+ secret_id_part, DIGEST_LEN);
|
|
|
+
|
|
|
+ desc_id = tor_malloc_zero(DIGEST_LEN);
|
|
|
+ rend_get_descriptor_id_bytes(desc_id, service_id, secret_id_part);
|
|
|
+ smartlist_add(desc_ids_out, desc_id);
|
|
|
+ base32_encode(desc_id_base32, REND_DESC_ID_V2_BASE32 + 1,
|
|
|
+ desc_id, DIGEST_LEN);
|
|
|
+
|
|
|
+ if (crypto_pk_write_public_key_to_string(desc->pk, &permanent_key,
|
|
|
+ &permanent_key_len) < 0) {
|
|
|
+ log_warn(LD_BUG, "Could not write public key to string.");
|
|
|
+ if (permanent_key) tor_free(permanent_key);
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ format_iso_time(published, desc->timestamp);
|
|
|
+
|
|
|
+ protocol_versions_written = 0;
|
|
|
+ for (i = 0; i < 8; i++) {
|
|
|
+ if (desc->protocols & 1 << i) {
|
|
|
+ tor_snprintf(protocol_versions_string + protocol_versions_written,
|
|
|
+ 16 - protocol_versions_written, "%d,", i);
|
|
|
+ protocol_versions_written += 2;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ protocol_versions_string[protocol_versions_written - 1] = 0;
|
|
|
+
|
|
|
+ desc_len = 2000 + desc->n_intro_points * 1000;
|
|
|
+ desc_str = tor_malloc_zero(desc_len);
|
|
|
+ result = tor_snprintf(desc_str, desc_len,
|
|
|
+ "rendezvous-service-descriptor %s\n"
|
|
|
+ "version 2\n"
|
|
|
+ "permanent-key\n%s"
|
|
|
+ "secret-id-part %s\n"
|
|
|
+ "publication-time %s\n"
|
|
|
+ "protocol-versions %s\n"
|
|
|
+ "introduction-points\n"
|
|
|
+ "-----BEGIN MESSAGE-----\n%s"
|
|
|
+ "-----END MESSAGE-----\n",
|
|
|
+ desc_id_base32,
|
|
|
+ permanent_key,
|
|
|
+ secret_id_part_base32,
|
|
|
+ published,
|
|
|
+ protocol_versions_string,
|
|
|
+ ipos_base64);
|
|
|
+ tor_free(permanent_key);
|
|
|
+ if (result < 0) {
|
|
|
+ log_warn(LD_BUG, "Descriptor ran out of room.");
|
|
|
+ if (desc_str) tor_free(desc_str);
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+ written = result;
|
|
|
+
|
|
|
+ strlcpy(desc_str + written, "signature\n", desc_len - written);
|
|
|
+ written += strlen(desc_str + written);
|
|
|
+ desc_str[written] = '\0';
|
|
|
+ if (crypto_digest(desc_digest, desc_str, written) < 0) {
|
|
|
+ log_warn(LD_BUG, "could not create digest.");
|
|
|
+ tor_free(desc_str);
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+ if (router_append_dirobj_signature(desc_str + written,
|
|
|
+ desc_len - written,
|
|
|
+ desc_digest, desc->pk) < 0) {
|
|
|
+ log_warn(LD_BUG, "Couldn't sign desc.");
|
|
|
+ tor_free(desc_str);
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+ written += strlen(desc_str+written);
|
|
|
+ if (written+2 > desc_len) {
|
|
|
+ log_warn(LD_BUG, "Could not finish desc.");
|
|
|
+ tor_free(desc_str);
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+ desc_str[written++] = '\n';
|
|
|
+ desc_str[written++] = 0;
|
|
|
+
|
|
|
+ if (!rend_desc_v2_is_parsable(desc_str)) {
|
|
|
+ log_warn(LD_BUG, "Could not parse my own descriptor: %s", desc_str);
|
|
|
+ tor_free(desc_str);
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+ smartlist_add(desc_strs_out, desc_str);
|
|
|
+ }
|
|
|
+
|
|
|
+ log_info(LD_REND, "Successfully encoded a v2 descriptor and "
|
|
|
+ "confirmed that it is parsable.");
|
|
|
+ goto done;
|
|
|
+
|
|
|
+ err:
|
|
|
+ SMARTLIST_FOREACH(desc_ids_out, void *, id, tor_free(id));
|
|
|
+ smartlist_clear(desc_ids_out);
|
|
|
+ SMARTLIST_FOREACH(desc_strs_out, void *, str, tor_free(str));
|
|
|
+ smartlist_clear(desc_strs_out);
|
|
|
+ seconds_valid = -1;
|
|
|
+
|
|
|
+ done:
|
|
|
+ tor_free(ipos_base64);
|
|
|
+ return seconds_valid;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
* <b>key</b>. Store the descriptor in *<b>str_out</b>, and set
|
|
|
* *<b>len_out</b> to its length.
|
|
|
*/
|