Commit c58675ca authored by Nick Mathewson's avatar Nick Mathewson 🤹
Browse files

r16236@catbus: nickm | 2007-10-28 14:36:30 -0400

 Patch from Karsten Loesing: encode and parse v2 rendezvous descriptors.


svn:r12254
parent 665aa765
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -271,7 +271,7 @@ $Id$

           [At start, exactly once]

           The identifier of this introduction point: the base-16 encoded
           The identifier of this introduction point: the base-32 encoded
           hash of this introduction point's identity key.

         "ip-address" ip-address NL
+45 −1
Original line number Diff line number Diff line
@@ -587,6 +587,27 @@ typedef enum {
/** Length of 'y' portion of 'y.onion' URL. */
#define REND_SERVICE_ID_LEN 16

/** Time period for which a v2 descriptor will be valid. */
#define REND_TIME_PERIOD_V2_DESC_VALIDITY (24*60*60)

/** Time period within which two sets of v2 descriptors will be uploaded in
 * parallel. */
#define REND_TIME_PERIOD_OVERLAPPING_V2_DESCS (60*60)

/** Number of non-consecutive replicas (i.e. distributed somewhere
 * in the ring) for a descriptor. */
#define REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS 2

/** Maximum time that an onion router may not respond unless taken
 * from the list of hidden service directories. */
#define REND_HS_DIR_REACHABLE_TIMEOUT (45*60)

/** Number of consecutive replicas for a descriptor. */
#define REND_NUMBER_OF_CONSECUTIVE_REPLICAS 3

/** Length of v2 descriptor ID (32 base32 chars = 160 bits). */
#define REND_DESC_ID_V2_BASE32 32

#define CELL_DIRECTION_IN 1
#define CELL_DIRECTION_OUT 2

@@ -3351,7 +3372,7 @@ int rend_client_send_introduction(origin_circuit_t *introcirc,
/** Information used to connect to a hidden service. */
typedef struct rend_service_descriptor_t {
  crypto_pk_env_t *pk; /**< This service's public key. */
  int version; /**< 0. */
  int version; /**< 0 or 2. */
  time_t timestamp; /**< Time when the descriptor was generated. */
  uint16_t protocols; /**< Bitmask: which rendezvous protocols are supported?
                       * (We allow bits '0', '1', and '2' to be set.) */
@@ -3365,6 +3386,8 @@ typedef struct rend_service_descriptor_t {
   * from this array if introduction attempts fail.  If this array is present,
   * its elements correspond to the elements of intro_points. */
  extend_info_t **intro_point_extend_info;
  strmap_t *intro_keys; /**< map from intro node hexdigest to key; only
                         * used for versioned hidden service descriptors. */
} rend_service_descriptor_t;

int rend_cmp_service_ids(const char *one, const char *two);
@@ -3399,6 +3422,17 @@ int rend_cache_lookup_entry(const char *query, int version,
                            rend_cache_entry_t **entry_out);
int rend_cache_store(const char *desc, size_t desc_len, int published);
int rend_cache_size(void);
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);
int rend_compute_v2_desc_id(char *desc_id_out, const char *service_id,
                            const char *descriptor_cookie,
                            time_t now, uint8_t replica);
int rend_id_is_in_interval(const char *a, const char *b, const char *c);
void rend_get_descriptor_id_bytes(char *descriptor_id_out,
                                  const char *service_id,
                                  const char *secret_id_part);

/********************************* rendservice.c ***************************/

@@ -3728,6 +3762,16 @@ ns_detached_signatures_t *networkstatus_parse_detached_signatures(

authority_cert_t *authority_cert_parse_from_string(const char *s,
                                                   const char **end_of_string);
int rend_parse_v2_service_descriptor(rend_service_descriptor_t **parsed_out,
                                     char *desc_id_out,
                                     char **intro_points_encrypted_out,
                                     size_t *intro_points_encrypted_size_out,
                                     size_t *encoded_size_out,
                                     const char **next_out, const char *desc);
int rend_decrypt_introduction_points(rend_service_descriptor_t *parsed,
                                     const char *descriptor_cookie,
                                     const char *intro_content,
                                     size_t intro_size);

#endif
+423 −1
Original line number Diff line number Diff line
@@ -43,7 +43,429 @@ rend_service_descriptor_free(rend_service_descriptor_t *desc)
  tor_free(desc);
}

/** Encode a V0 service descriptor for <b>desc</b>, and sign it with
/* Length of a binary-encoded rendezvous service ID. */
#define REND_SERVICE_ID_BINARY 10

/* Length of the time period that is used to encode the secret ID part of
 * versioned hidden service descriptors. */
#define REND_TIME_PERIOD_BINARY 4

/* Length of the descriptor cookie that is used for versioned hidden
 * service descriptors. */
#define REND_DESC_COOKIE_BINARY 16

/* Length of the replica number that is used to determine the secret ID
 * part of versioned hidden service descriptors. */
#define REND_REPLICA_BINARY 1

/* Length of the base32-encoded secret ID part of versioned hidden service
 * descriptors. */
#define REND_SECRET_ID_PART_BASE32 32

/* Compute the descriptor ID for <b>service_id</b> of length
 * <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);
}

/* Compute the secret ID part for <b>time_period</b> of length
 * <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);
}

/* Compute the time period bytes for time <b>now</b> plus a potentially
 * 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);
}

/* Compute the time in seconds that a descriptor that is generated
 * <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;
}

/* Compute the binary <b>desc_id</b> for a given base32-encoded
 * <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;
  }
  /* Convert service ID to binary. */
  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;
  }
  /* Calculate current time-period. */
  get_time_period_bytes(time_period, now, 0, service_id_binary);
  /* Calculate secret-id-part = h(time-period + replica). */
  get_secret_id_part_bytes(secret_id_part, time_period, descriptor_cookie,
                           replica);
  /* Calculate descriptor ID. */
  rend_get_descriptor_id_bytes(desc_id_out, service_id_binary, secret_id_part);
  return 0;
}

/* Encode the introduction points in <b>desc</b>, optionally encrypt them
 * 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;
  /* Assemble unencrypted list of introduction points. */
  unenc_len = desc->n_intro_points * 1000; /* too long, but ok. */
  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];
    /* Obtain extend info with introduction point details. */
    extend_info_t *info = desc->intro_point_extend_info[i];
    /* Encode introduction point ID. */
    base32_encode(id_base32, 32 + 1, info->identity_digest, DIGEST_LEN);
    /* Encode onion key. */
    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;
    }
    /* Encode intro key. */
    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;
    }
    /* Assemble everything for this introduction point. */
    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;
    }
    /* Update total number of written bytes for unencrypted intro points. */
    unenc_written += res;
  }
  /* Finalize unencrypted introduction points. */
  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 a descriptor cookie is passed, encrypt introduction points. */
  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;
    }
    /* Replace original string by encrypted one. */
    unenc = enc;
    unenc_written = enclen;
  }
  /* Base64-encode introduction points. */
  *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;
}

/** Attempt to parse the given <b>desc_str</b> and return true if this
 * 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);
}

/** Encode a set of new service descriptors 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.), 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;
  }
  /* Obtain service_id from public key. */
  crypto_pk_get_digest(desc->pk, service_id);
  /* Calculate current time-period. */
  get_time_period_bytes(time_period, now, period, service_id);
  /* Determine how many seconds the descriptor will be valid. */
  seconds_valid = period * REND_TIME_PERIOD_V2_DESC_VALIDITY +
                  get_seconds_valid(now, service_id);
  /* Assemble, possibly encrypt, and encode introduction points. */
  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;
  }
  /* Encode REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS descriptors. */
  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]; /* max len: "0,1,2,3,4,5,6,7\0" */
    size_t protocol_versions_written;
    size_t desc_len;
    char *desc_str;
    int result = 0;
    size_t written = 0;
    char desc_digest[DIGEST_LEN];
    /* Calculate secret-id-part = h(time-period + cookie + replica). */
    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);
    /* Calculate descriptor ID. */
    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);
    /* PEM-encode the public key */
    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;
    }
    /* Encode timestamp. */
    format_iso_time(published, desc->timestamp);
    /* Write protocol-versions bitmask to comma-separated value string. */
    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;
    /* Assemble complete descriptor. */
    desc_len = 2000 + desc->n_intro_points * 1000; /* far too long, but ok. */
    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;
    /* Add signature. */
    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;
    /* Check if we can parse our own descriptor. */
    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;
}

/** Encode a service descriptor for <b>desc</b>, and sign it with
 * <b>key</b>. Store the descriptor in *<b>str_out</b>, and set
 * *<b>len_out</b> to its length.
 */
+15 −0
Original line number Diff line number Diff line
@@ -51,6 +51,8 @@ typedef struct rend_service_t {
  char pk_digest[DIGEST_LEN];
  smartlist_t *intro_nodes; /**< list of hexdigests for intro points we have,
                             * or are trying to establish. */
  strmap_t *intro_keys; /**< map from intro node hexdigest to key; only
                          * used for versioned hidden service descriptors. */
  time_t intro_period_started;
  int n_intro_circuits_launched; /**< count of intro circuits we have
                                  * established in this period. */
@@ -72,6 +74,15 @@ num_rend_services(void)
  return smartlist_len(rend_service_list);
}

/** Release the storage held by the intro key in <b>_ent</b>.
 */
static void
intro_key_free(void *_ent)
{
  crypto_pk_env_t *ent = _ent;
  crypto_free_pk_env(ent);
}

/** Release the storage held by <b>service</b>.
 */
static void
@@ -83,6 +94,8 @@ rend_service_free(rend_service_t *service)
  smartlist_free(service->ports);
  if (service->private_key)
    crypto_free_pk_env(service->private_key);
  if (service->intro_keys)
    strmap_free(service->intro_keys, intro_key_free);
  tor_free(service->intro_prefer_nodes);
  tor_free(service->intro_exclude_nodes);
  SMARTLIST_FOREACH(service->intro_nodes, void*, p, tor_free(p));
@@ -119,6 +132,7 @@ add_service(rend_service_t *service)
    service->intro_prefer_nodes = tor_strdup("");
  if (!service->intro_exclude_nodes)
    service->intro_exclude_nodes = tor_strdup("");
  service->intro_keys = strmap_new();

  if (!smartlist_len(service->ports)) {
    log_warn(LD_CONFIG, "Hidden service with no ports configured; ignoring.");
@@ -303,6 +317,7 @@ rend_service_update_descriptor(rend_service_t *service)
  d->intro_point_extend_info = tor_malloc_zero(sizeof(extend_info_t*)*n);
  /* We support intro protocol 2 and protocol 0. */
  d->protocols = (1<<2) | (1<<0);
  d->intro_keys = service->intro_keys;
  for (i=0; i < n; ++i) {
    const char *name = smartlist_get(service->intro_nodes, i);
    router = router_get_by_nickname(name, 1);
+346 −0

File changed.

Preview size limit exceeded, changes collapsed.

Loading