tor

The Tor anonymity network
git clone https://git.dasho.dev/tor.git
Log | Files | Refs | README | LICENSE

microdesc_parse.c (11203B)


      1 /* Copyright (c) 2001 Matej Pfajfar.
      2 * Copyright (c) 2001-2004, Roger Dingledine.
      3 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
      4 * Copyright (c) 2007-2021, The Tor Project, Inc. */
      5 /* See LICENSE for licensing information */
      6 
      7 /**
      8 * \file microdesc_parse.c
      9 * \brief Code to parse and validate microdescriptors.
     10 **/
     11 
     12 #include "core/or/or.h"
     13 
     14 #include "app/config/config.h"
     15 #include "core/or/policies.h"
     16 #include "feature/dirparse/microdesc_parse.h"
     17 #include "feature/dirparse/parsecommon.h"
     18 #include "feature/dirparse/routerparse.h"
     19 #include "feature/nodelist/microdesc.h"
     20 #include "feature/nodelist/nickname.h"
     21 #include "feature/nodelist/nodefamily.h"
     22 #include "feature/relay/router.h"
     23 #include "lib/crypt_ops/crypto_curve25519.h"
     24 #include "lib/crypt_ops/crypto_ed25519.h"
     25 #include "lib/crypt_ops/crypto_format.h"
     26 #include "lib/memarea/memarea.h"
     27 
     28 #include "feature/nodelist/microdesc_st.h"
     29 
     30 /** List of tokens recognized in microdescriptors */
     31 // clang-format off
     32 static token_rule_t microdesc_token_table[] = {
     33  T1_START("onion-key",        K_ONION_KEY,        NO_ARGS,     OPT_KEY_1024),
     34  T1("ntor-onion-key",         K_ONION_KEY_NTOR,   GE(1),       NO_OBJ ),
     35  T0N("id",                    K_ID,               GE(2),       NO_OBJ ),
     36  T0N("a",                     K_A,                GE(1),       NO_OBJ ),
     37  T01("family",                K_FAMILY,           CONCAT_ARGS, NO_OBJ ),
     38  T01("family-ids",            K_FAMILY_IDS,       CONCAT_ARGS, NO_OBJ ),
     39  T01("p",                     K_P,                CONCAT_ARGS, NO_OBJ ),
     40  T01("p6",                    K_P6,               CONCAT_ARGS, NO_OBJ ),
     41  A01("@last-listed",          A_LAST_LISTED,      CONCAT_ARGS, NO_OBJ ),
     42  END_OF_TABLE
     43 };
     44 // clang-format on
     45 
     46 /** Assuming that s starts with a microdesc, return the start of the
     47 * *NEXT* one.  Return NULL on "not found." */
     48 static const char *
     49 find_start_of_next_microdesc(const char *s, const char *eos)
     50 {
     51  int started_with_annotations;
     52  s = eat_whitespace_eos(s, eos);
     53  if (!s)
     54    return NULL;
     55 
     56 #define CHECK_LENGTH() STMT_BEGIN \
     57    if (eos - s < 32)             \
     58      return NULL;                \
     59  STMT_END
     60 
     61 #define NEXT_LINE() STMT_BEGIN            \
     62    s = memchr(s, '\n', eos-s);           \
     63    if (!s || eos - s <= 1)               \
     64      return NULL;                        \
     65    s++;                                  \
     66  STMT_END
     67 
     68  CHECK_LENGTH();
     69 
     70  started_with_annotations = (*s == '@');
     71 
     72  if (started_with_annotations) {
     73    /* Start by advancing to the first non-annotation line. */
     74    while (*s == '@')
     75      NEXT_LINE();
     76  }
     77  CHECK_LENGTH();
     78 
     79  /* Now we should be pointed at an onion-key line.  If we are, then skip
     80   * it. */
     81  if (!strcmpstart(s, "onion-key"))
     82    NEXT_LINE();
     83 
     84  /* Okay, now we're pointed at the first line of the microdescriptor which is
     85     not an annotation or onion-key.  The next line that _is_ an annotation or
     86     onion-key is the start of the next microdescriptor. */
     87  while (eos - s > 32) {
     88    if (*s == '@' || !strcmpstart(s, "onion-key"))
     89      return s;
     90    NEXT_LINE();
     91  }
     92  return NULL;
     93 
     94 #undef CHECK_LENGTH
     95 #undef NEXT_LINE
     96 }
     97 
     98 static inline int
     99 policy_is_reject_star_or_null(struct short_policy_t *policy)
    100 {
    101  return !policy || short_policy_is_reject_star(policy);
    102 }
    103 
    104 /**
    105 * Return a human-readable description of a given saved_location_t.
    106 * Never returns NULL.
    107 **/
    108 static const char *
    109 saved_location_to_string(saved_location_t where)
    110 {
    111  const char *location;
    112  switch (where) {
    113    case SAVED_NOWHERE:
    114      location = "download or generated string";
    115      break;
    116    case SAVED_IN_CACHE:
    117      location = "cache";
    118      break;
    119    case SAVED_IN_JOURNAL:
    120      location = "journal";
    121      break;
    122    default:
    123      location = "unknown location";
    124      break;
    125  }
    126  return location;
    127 }
    128 
    129 /**
    130 * Given a microdescriptor stored in <b>where</b> which starts at <b>s</b>,
    131 * which ends at <b>start_of_next_microdescriptor</b>, and which is located
    132 * within a larger document beginning at <b>start</b>: Fill in the body,
    133 * bodylen, bodylen, saved_location, off, and digest fields of <b>md</b> as
    134 * appropriate.
    135 *
    136 * The body field will be an alias within <b>s</b> if <b>saved_location</b>
    137 * is SAVED_IN_CACHE, and will be copied into body and nul-terminated
    138 * otherwise.
    139 **/
    140 static int
    141 microdesc_extract_body(microdesc_t *md,
    142                       const char *start,
    143                       const char *s, const char *start_of_next_microdesc,
    144                       saved_location_t where)
    145 {
    146  const bool copy_body = (where != SAVED_IN_CACHE);
    147 
    148  const char *cp = tor_memstr(s, start_of_next_microdesc-s, "onion-key");
    149 
    150  const bool no_onion_key = (cp == NULL);
    151  if (no_onion_key) {
    152    cp = s; /* So that we have *some* junk to put in the body */
    153  }
    154 
    155  md->bodylen = start_of_next_microdesc - cp;
    156  md->saved_location = where;
    157  if (copy_body)
    158    md->body = tor_memdup_nulterm(cp, md->bodylen);
    159  else
    160    md->body = (char*)cp;
    161  md->off = cp - start;
    162 
    163  crypto_digest256(md->digest, md->body, md->bodylen, DIGEST_SHA256);
    164 
    165  return no_onion_key ? -1 : 0;
    166 }
    167 
    168 /**
    169 * Parse a microdescriptor which begins at <b>s</b> and ends at
    170 * <b>start_of_next_microdesc</b>.  Store its fields into <b>md</b>.  Use
    171 * <b>where</b> for generating log information.  If <b>allow_annotations</b>
    172 * is true, then one or more annotations may precede the microdescriptor body
    173 * proper.  Use <b>area</b> for memory management, clearing it when done.
    174 *
    175 * On success, return 0; otherwise return -1.
    176 **/
    177 static int
    178 microdesc_parse_fields(microdesc_t *md,
    179                       memarea_t *area,
    180                       const char *s, const char *start_of_next_microdesc,
    181                       int allow_annotations,
    182                       saved_location_t where)
    183 {
    184  smartlist_t *tokens = smartlist_new();
    185  int rv = -1;
    186  int flags = allow_annotations ? TS_ANNOTATIONS_OK : 0;
    187  directory_token_t *tok;
    188 
    189  if (tokenize_string(area, s, start_of_next_microdesc, tokens,
    190                      microdesc_token_table, flags)) {
    191    log_warn(LD_DIR, "Unparseable microdescriptor found in %s",
    192             saved_location_to_string(where));
    193    goto err;
    194  }
    195 
    196  if ((tok = find_opt_by_keyword(tokens, A_LAST_LISTED))) {
    197    if (parse_iso_time(tok->args[0], &md->last_listed)) {
    198      log_warn(LD_DIR, "Bad last-listed time in microdescriptor");
    199      goto err;
    200    }
    201  }
    202 
    203  tok = find_by_keyword(tokens, K_ONION_KEY);
    204  if (tok && tok->key && !crypto_pk_public_exponent_ok(tok->key)) {
    205    log_warn(LD_DIR,
    206             "Relay's onion key had invalid exponent.");
    207    goto err;
    208  }
    209 
    210  if ((tok = find_opt_by_keyword(tokens, K_ONION_KEY_NTOR))) {
    211    curve25519_public_key_t k;
    212    tor_assert(tok->n_args >= 1);
    213    if (curve25519_public_from_base64(&k, tok->args[0]) < 0) {
    214      log_warn(LD_DIR, "Bogus ntor-onion-key in microdesc");
    215      goto err;
    216    }
    217    md->onion_curve25519_pkey =
    218      tor_memdup(&k, sizeof(curve25519_public_key_t));
    219  }
    220 
    221  smartlist_t *id_lines = find_all_by_keyword(tokens, K_ID);
    222  if (id_lines) {
    223    SMARTLIST_FOREACH_BEGIN(id_lines, directory_token_t *, t) {
    224      tor_assert(t->n_args >= 2);
    225      if (!strcmp(t->args[0], "ed25519")) {
    226        if (md->ed25519_identity_pkey) {
    227          log_warn(LD_DIR, "Extra ed25519 key in microdesc");
    228          smartlist_free(id_lines);
    229          goto err;
    230        }
    231        ed25519_public_key_t k;
    232        if (ed25519_public_from_base64(&k, t->args[1])<0) {
    233          log_warn(LD_DIR, "Bogus ed25519 key in microdesc");
    234          smartlist_free(id_lines);
    235          goto err;
    236        }
    237        md->ed25519_identity_pkey = tor_memdup(&k, sizeof(k));
    238      }
    239    } SMARTLIST_FOREACH_END(t);
    240    smartlist_free(id_lines);
    241  }
    242 
    243  {
    244    smartlist_t *a_lines = find_all_by_keyword(tokens, K_A);
    245    if (a_lines) {
    246      find_single_ipv6_orport(a_lines, &md->ipv6_addr, &md->ipv6_orport);
    247      smartlist_free(a_lines);
    248    }
    249  }
    250 
    251  if ((tok = find_opt_by_keyword(tokens, K_FAMILY))) {
    252    md->family = nodefamily_parse(tok->args[0],
    253                                  NULL,
    254                                  NF_WARN_MALFORMED);
    255  }
    256  if ((tok = find_opt_by_keyword(tokens, K_FAMILY_IDS))) {
    257    smartlist_t *ids = smartlist_new();
    258    smartlist_split_string(ids, tok->args[0], " ",
    259                           SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
    260    if (smartlist_len(ids) > 0) {
    261      md->family_ids = ids;
    262    } else {
    263      smartlist_free(ids);
    264    }
    265  }
    266 
    267  if ((tok = find_opt_by_keyword(tokens, K_P))) {
    268    md->exit_policy = parse_short_policy(tok->args[0]);
    269  }
    270  if ((tok = find_opt_by_keyword(tokens, K_P6))) {
    271    md->ipv6_exit_policy = parse_short_policy(tok->args[0]);
    272  }
    273 
    274  if (policy_is_reject_star_or_null(md->exit_policy) &&
    275      policy_is_reject_star_or_null(md->ipv6_exit_policy)) {
    276    md->policy_is_reject_star = 1;
    277  }
    278 
    279  rv = 0;
    280 err:
    281 
    282  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    283  memarea_clear(area);
    284  smartlist_free(tokens);
    285 
    286  return rv;
    287 }
    288 
    289 /** Parse as many microdescriptors as are found from the string starting at
    290 * <b>s</b> and ending at <b>eos</b>.  If allow_annotations is set, read any
    291 * annotations we recognize and ignore ones we don't.
    292 *
    293 * If <b>saved_location</b> isn't SAVED_IN_CACHE, make a local copy of each
    294 * descriptor in the body field of each microdesc_t.
    295 *
    296 * Return all newly parsed microdescriptors in a newly allocated
    297 * smartlist_t. If <b>invalid_disgests_out</b> is provided, add a SHA256
    298 * microdesc digest to it for every microdesc that we found to be badly
    299 * formed. (This may cause duplicates) */
    300 smartlist_t *
    301 microdescs_parse_from_string(const char *s, const char *eos,
    302                             int allow_annotations,
    303                             saved_location_t where,
    304                             smartlist_t *invalid_digests_out)
    305 {
    306  smartlist_t *result;
    307  microdesc_t *md = NULL;
    308  memarea_t *area;
    309  const char *start = s;
    310  const char *start_of_next_microdesc;
    311 
    312  if (!eos)
    313    eos = s + strlen(s);
    314 
    315  s = eat_whitespace_eos(s, eos);
    316  area = memarea_new();
    317  result = smartlist_new();
    318 
    319  while (s < eos) {
    320   bool okay = false;
    321 
    322    start_of_next_microdesc = find_start_of_next_microdesc(s, eos);
    323    if (!start_of_next_microdesc)
    324      start_of_next_microdesc = eos;
    325 
    326    md = tor_malloc_zero(sizeof(microdesc_t));
    327    uint8_t md_digest[DIGEST256_LEN];
    328    {
    329      const bool body_not_found =
    330        microdesc_extract_body(md, start, s,
    331                               start_of_next_microdesc,
    332                               where) < 0;
    333 
    334      memcpy(md_digest, md->digest, DIGEST256_LEN);
    335      if (body_not_found) {
    336        log_fn(LOG_PROTOCOL_WARN, LD_DIR, "Malformed or truncated descriptor");
    337        goto next;
    338      }
    339    }
    340 
    341    if (microdesc_parse_fields(md, area, s, start_of_next_microdesc,
    342                               allow_annotations, where) == 0) {
    343      smartlist_add(result, md);
    344      md = NULL; // prevent free
    345      okay = true;
    346    }
    347 
    348  next:
    349    if (! okay && invalid_digests_out) {
    350      smartlist_add(invalid_digests_out,
    351                    tor_memdup(md_digest, DIGEST256_LEN));
    352    }
    353    microdesc_free(md);
    354    md = NULL;
    355    s = start_of_next_microdesc;
    356  }
    357 
    358  memarea_drop_all(area);
    359 
    360  return result;
    361 }