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