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 }