diff --git a/src/core/include.am b/src/core/include.am index febc1d821e..89b12e9582 100644 --- a/src/core/include.am +++ b/src/core/include.am @@ -74,6 +74,7 @@ LIBTOR_APP_A_SOURCES = \ src/feature/dircommon/fp_pair.c \ src/feature/dircommon/voting_schedule.c \ src/feature/dirparse/authcert_parse.c \ + src/feature/dirparse/microdesc_parse.c \ src/feature/dirparse/parsecommon.c \ src/feature/dirparse/routerparse.c \ src/feature/dirparse/sigcommon.c \ @@ -294,6 +295,7 @@ noinst_HEADERS += \ src/feature/dircommon/voting_schedule.h \ src/feature/dirparse/authcert_members.i \ src/feature/dirparse/authcert_parse.h \ + src/feature/dirparse/microdesc_parse.h \ src/feature/dirparse/parsecommon.h \ src/feature/dirparse/routerparse.h \ src/feature/dirparse/sigcommon.h \ diff --git a/src/feature/dirauth/dirvote.c b/src/feature/dirauth/dirvote.c index f5546abd40..27a85de8ad 100644 --- a/src/feature/dirauth/dirvote.c +++ b/src/feature/dirauth/dirvote.c @@ -28,6 +28,7 @@ #include "feature/nodelist/dirlist.h" #include "feature/nodelist/routerlist.h" #include "feature/dirparse/routerparse.h" +#include "feature/dirparse/microdesc_parse.h" #include "feature/dirparse/signing.h" #include "feature/nodelist/fmt_routerstatus.h" #include "feature/client/entrynodes.h" /* needed for guardfraction methods */ diff --git a/src/feature/dirparse/microdesc_parse.c b/src/feature/dirparse/microdesc_parse.c new file mode 100644 index 0000000000..005d2c53d0 --- /dev/null +++ b/src/feature/dirparse/microdesc_parse.c @@ -0,0 +1,267 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file microdesc_parse.c + * \brief Code to parse and validate microdescriptors. + **/ + +#include "core/or/or.h" + +#include "app/config/config.h" +#include "core/or/policies.h" +#include "feature/dirparse/microdesc_parse.h" +#include "feature/dirparse/parsecommon.h" +#include "feature/dirparse/routerparse.h" +#include "feature/nodelist/microdesc.h" +#include "feature/nodelist/nickname.h" +#include "feature/relay/router.h" +#include "lib/crypt_ops/crypto_curve25519.h" +#include "lib/crypt_ops/crypto_ed25519.h" +#include "lib/crypt_ops/crypto_format.h" +#include "lib/memarea/memarea.h" + +#include "feature/nodelist/microdesc_st.h" + +/** List of tokens recognized in microdescriptors */ +static token_rule_t microdesc_token_table[] = { + T1_START("onion-key", K_ONION_KEY, NO_ARGS, NEED_KEY_1024), + T01("ntor-onion-key", K_ONION_KEY_NTOR, GE(1), NO_OBJ ), + T0N("id", K_ID, GE(2), NO_OBJ ), + T0N("a", K_A, GE(1), NO_OBJ ), + T01("family", K_FAMILY, ARGS, NO_OBJ ), + T01("p", K_P, CONCAT_ARGS, NO_OBJ ), + T01("p6", K_P6, CONCAT_ARGS, NO_OBJ ), + A01("@last-listed", A_LAST_LISTED, CONCAT_ARGS, NO_OBJ ), + END_OF_TABLE +}; + +/** Assuming that s starts with a microdesc, return the start of the + * *NEXT* one. Return NULL on "not found." */ +static const char * +find_start_of_next_microdesc(const char *s, const char *eos) +{ + int started_with_annotations; + s = eat_whitespace_eos(s, eos); + if (!s) + return NULL; + +#define CHECK_LENGTH() STMT_BEGIN \ + if (s+32 > eos) \ + return NULL; \ + STMT_END + +#define NEXT_LINE() STMT_BEGIN \ + s = memchr(s, '\n', eos-s); \ + if (!s || s+1 >= eos) \ + return NULL; \ + s++; \ + STMT_END + + CHECK_LENGTH(); + + started_with_annotations = (*s == '@'); + + if (started_with_annotations) { + /* Start by advancing to the first non-annotation line. */ + while (*s == '@') + NEXT_LINE(); + } + CHECK_LENGTH(); + + /* Now we should be pointed at an onion-key line. If we are, then skip + * it. */ + if (!strcmpstart(s, "onion-key")) + NEXT_LINE(); + + /* Okay, now we're pointed at the first line of the microdescriptor which is + not an annotation or onion-key. The next line that _is_ an annotation or + onion-key is the start of the next microdescriptor. */ + while (s+32 < eos) { + if (*s == '@' || !strcmpstart(s, "onion-key")) + return s; + NEXT_LINE(); + } + return NULL; + +#undef CHECK_LENGTH +#undef NEXT_LINE +} + +/** Parse as many microdescriptors as are found from the string starting at + * s and ending at eos. If allow_annotations is set, read any + * annotations we recognize and ignore ones we don't. + * + * If saved_location isn't SAVED_IN_CACHE, make a local copy of each + * descriptor in the body field of each microdesc_t. + * + * Return all newly parsed microdescriptors in a newly allocated + * smartlist_t. If invalid_disgests_out is provided, add a SHA256 + * microdesc digest to it for every microdesc that we found to be badly + * formed. (This may cause duplicates) */ +smartlist_t * +microdescs_parse_from_string(const char *s, const char *eos, + int allow_annotations, + saved_location_t where, + smartlist_t *invalid_digests_out) +{ + smartlist_t *tokens; + smartlist_t *result; + microdesc_t *md = NULL; + memarea_t *area; + const char *start = s; + const char *start_of_next_microdesc; + int flags = allow_annotations ? TS_ANNOTATIONS_OK : 0; + const int copy_body = (where != SAVED_IN_CACHE); + + directory_token_t *tok; + + if (!eos) + eos = s + strlen(s); + + s = eat_whitespace_eos(s, eos); + area = memarea_new(); + result = smartlist_new(); + tokens = smartlist_new(); + + while (s < eos) { + int okay = 0; + + start_of_next_microdesc = find_start_of_next_microdesc(s, eos); + if (!start_of_next_microdesc) + start_of_next_microdesc = eos; + + md = tor_malloc_zero(sizeof(microdesc_t)); + { + const char *cp = tor_memstr(s, start_of_next_microdesc-s, + "onion-key"); + const int no_onion_key = (cp == NULL); + if (no_onion_key) { + cp = s; /* So that we have *some* junk to put in the body */ + } + + md->bodylen = start_of_next_microdesc - cp; + md->saved_location = where; + if (copy_body) + md->body = tor_memdup_nulterm(cp, md->bodylen); + else + md->body = (char*)cp; + md->off = cp - start; + crypto_digest256(md->digest, md->body, md->bodylen, DIGEST_SHA256); + if (no_onion_key) { + log_fn(LOG_PROTOCOL_WARN, LD_DIR, "Malformed or truncated descriptor"); + goto next; + } + } + + if (tokenize_string(area, s, start_of_next_microdesc, tokens, + microdesc_token_table, flags)) { + log_warn(LD_DIR, "Unparseable microdescriptor"); + goto next; + } + + if ((tok = find_opt_by_keyword(tokens, A_LAST_LISTED))) { + if (parse_iso_time(tok->args[0], &md->last_listed)) { + log_warn(LD_DIR, "Bad last-listed time in microdescriptor"); + goto next; + } + } + + tok = find_by_keyword(tokens, K_ONION_KEY); + if (!crypto_pk_public_exponent_ok(tok->key)) { + log_warn(LD_DIR, + "Relay's onion key had invalid exponent."); + goto next; + } + router_set_rsa_onion_pkey(tok->key, &md->onion_pkey, + &md->onion_pkey_len); + crypto_pk_free(tok->key); + + if ((tok = find_opt_by_keyword(tokens, K_ONION_KEY_NTOR))) { + curve25519_public_key_t k; + tor_assert(tok->n_args >= 1); + if (curve25519_public_from_base64(&k, tok->args[0]) < 0) { + log_warn(LD_DIR, "Bogus ntor-onion-key in microdesc"); + goto next; + } + md->onion_curve25519_pkey = + tor_memdup(&k, sizeof(curve25519_public_key_t)); + } + + smartlist_t *id_lines = find_all_by_keyword(tokens, K_ID); + if (id_lines) { + SMARTLIST_FOREACH_BEGIN(id_lines, directory_token_t *, t) { + tor_assert(t->n_args >= 2); + if (!strcmp(t->args[0], "ed25519")) { + if (md->ed25519_identity_pkey) { + log_warn(LD_DIR, "Extra ed25519 key in microdesc"); + smartlist_free(id_lines); + goto next; + } + ed25519_public_key_t k; + if (ed25519_public_from_base64(&k, t->args[1])<0) { + log_warn(LD_DIR, "Bogus ed25519 key in microdesc"); + smartlist_free(id_lines); + goto next; + } + md->ed25519_identity_pkey = tor_memdup(&k, sizeof(k)); + } + } SMARTLIST_FOREACH_END(t); + smartlist_free(id_lines); + } + + { + smartlist_t *a_lines = find_all_by_keyword(tokens, K_A); + if (a_lines) { + find_single_ipv6_orport(a_lines, &md->ipv6_addr, &md->ipv6_orport); + smartlist_free(a_lines); + } + } + + if ((tok = find_opt_by_keyword(tokens, K_FAMILY))) { + int i; + md->family = smartlist_new(); + for (i=0;in_args;++i) { + if (!is_legal_nickname_or_hexdigest(tok->args[i])) { + log_warn(LD_DIR, "Illegal nickname %s in family line", + escaped(tok->args[i])); + goto next; + } + smartlist_add_strdup(md->family, tok->args[i]); + } + } + + if ((tok = find_opt_by_keyword(tokens, K_P))) { + md->exit_policy = parse_short_policy(tok->args[0]); + } + if ((tok = find_opt_by_keyword(tokens, K_P6))) { + md->ipv6_exit_policy = parse_short_policy(tok->args[0]); + } + + smartlist_add(result, md); + okay = 1; + + md = NULL; + next: + if (! okay && invalid_digests_out) { + smartlist_add(invalid_digests_out, + tor_memdup(md->digest, DIGEST256_LEN)); + } + microdesc_free(md); + md = NULL; + + SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); + memarea_clear(area); + smartlist_clear(tokens); + s = start_of_next_microdesc; + } + + SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); + memarea_drop_all(area); + smartlist_free(tokens); + + return result; +} diff --git a/src/feature/dirparse/microdesc_parse.h b/src/feature/dirparse/microdesc_parse.h new file mode 100644 index 0000000000..e8f8b2e17e --- /dev/null +++ b/src/feature/dirparse/microdesc_parse.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file microdesc_parse.h + * \brief Header file for microdesc_parse.c. + **/ + +#ifndef TOR_MICRODESC_PARSE_H +#define TOR_MICRODESC_PARSE_H + +smartlist_t *microdescs_parse_from_string(const char *s, const char *eos, + int allow_annotations, + saved_location_t where, + smartlist_t *invalid_digests_out); + +#endif diff --git a/src/feature/dirparse/routerparse.c b/src/feature/dirparse/routerparse.c index f661042fe2..599f2147cc 100644 --- a/src/feature/dirparse/routerparse.c +++ b/src/feature/dirparse/routerparse.c @@ -309,19 +309,6 @@ static token_rule_t networkstatus_detached_signature_token_table[] = { END_OF_TABLE }; -/** List of tokens recognized in microdescriptors */ -static token_rule_t microdesc_token_table[] = { - T1_START("onion-key", K_ONION_KEY, NO_ARGS, NEED_KEY_1024), - T01("ntor-onion-key", K_ONION_KEY_NTOR, GE(1), NO_OBJ ), - T0N("id", K_ID, GE(2), NO_OBJ ), - T0N("a", K_A, GE(1), NO_OBJ ), - T01("family", K_FAMILY, ARGS, NO_OBJ ), - T01("p", K_P, CONCAT_ARGS, NO_OBJ ), - T01("p6", K_P6, CONCAT_ARGS, NO_OBJ ), - A01("@last-listed", A_LAST_LISTED, CONCAT_ARGS, NO_OBJ ), - END_OF_TABLE -}; - #undef T /* static function prototypes */ @@ -541,7 +528,7 @@ router_parse_list_from_string(const char **s, const char *eos, * with at least one argument (use GE(1) in setup). If found, store * address and port number to addr_out and * port_out. Return number of OR ports found. */ -static int +int find_single_ipv6_orport(const smartlist_t *list, tor_addr_t *addr_out, uint16_t *port_out) @@ -3387,233 +3374,6 @@ find_all_exitpolicy(smartlist_t *s) return out; } -/** Assuming that s starts with a microdesc, return the start of the - * *NEXT* one. Return NULL on "not found." */ -static const char * -find_start_of_next_microdesc(const char *s, const char *eos) -{ - int started_with_annotations; - s = eat_whitespace_eos(s, eos); - if (!s) - return NULL; - -#define CHECK_LENGTH() STMT_BEGIN \ - if (s+32 > eos) \ - return NULL; \ - STMT_END - -#define NEXT_LINE() STMT_BEGIN \ - s = memchr(s, '\n', eos-s); \ - if (!s || s+1 >= eos) \ - return NULL; \ - s++; \ - STMT_END - - CHECK_LENGTH(); - - started_with_annotations = (*s == '@'); - - if (started_with_annotations) { - /* Start by advancing to the first non-annotation line. */ - while (*s == '@') - NEXT_LINE(); - } - CHECK_LENGTH(); - - /* Now we should be pointed at an onion-key line. If we are, then skip - * it. */ - if (!strcmpstart(s, "onion-key")) - NEXT_LINE(); - - /* Okay, now we're pointed at the first line of the microdescriptor which is - not an annotation or onion-key. The next line that _is_ an annotation or - onion-key is the start of the next microdescriptor. */ - while (s+32 < eos) { - if (*s == '@' || !strcmpstart(s, "onion-key")) - return s; - NEXT_LINE(); - } - return NULL; - -#undef CHECK_LENGTH -#undef NEXT_LINE -} - -/** Parse as many microdescriptors as are found from the string starting at - * s and ending at eos. If allow_annotations is set, read any - * annotations we recognize and ignore ones we don't. - * - * If saved_location isn't SAVED_IN_CACHE, make a local copy of each - * descriptor in the body field of each microdesc_t. - * - * Return all newly parsed microdescriptors in a newly allocated - * smartlist_t. If invalid_disgests_out is provided, add a SHA256 - * microdesc digest to it for every microdesc that we found to be badly - * formed. (This may cause duplicates) */ -smartlist_t * -microdescs_parse_from_string(const char *s, const char *eos, - int allow_annotations, - saved_location_t where, - smartlist_t *invalid_digests_out) -{ - smartlist_t *tokens; - smartlist_t *result; - microdesc_t *md = NULL; - memarea_t *area; - const char *start = s; - const char *start_of_next_microdesc; - int flags = allow_annotations ? TS_ANNOTATIONS_OK : 0; - const int copy_body = (where != SAVED_IN_CACHE); - - directory_token_t *tok; - - if (!eos) - eos = s + strlen(s); - - s = eat_whitespace_eos(s, eos); - area = memarea_new(); - result = smartlist_new(); - tokens = smartlist_new(); - - while (s < eos) { - int okay = 0; - - start_of_next_microdesc = find_start_of_next_microdesc(s, eos); - if (!start_of_next_microdesc) - start_of_next_microdesc = eos; - - md = tor_malloc_zero(sizeof(microdesc_t)); - { - const char *cp = tor_memstr(s, start_of_next_microdesc-s, - "onion-key"); - const int no_onion_key = (cp == NULL); - if (no_onion_key) { - cp = s; /* So that we have *some* junk to put in the body */ - } - - md->bodylen = start_of_next_microdesc - cp; - md->saved_location = where; - if (copy_body) - md->body = tor_memdup_nulterm(cp, md->bodylen); - else - md->body = (char*)cp; - md->off = cp - start; - crypto_digest256(md->digest, md->body, md->bodylen, DIGEST_SHA256); - if (no_onion_key) { - log_fn(LOG_PROTOCOL_WARN, LD_DIR, "Malformed or truncated descriptor"); - goto next; - } - } - - if (tokenize_string(area, s, start_of_next_microdesc, tokens, - microdesc_token_table, flags)) { - log_warn(LD_DIR, "Unparseable microdescriptor"); - goto next; - } - - if ((tok = find_opt_by_keyword(tokens, A_LAST_LISTED))) { - if (parse_iso_time(tok->args[0], &md->last_listed)) { - log_warn(LD_DIR, "Bad last-listed time in microdescriptor"); - goto next; - } - } - - tok = find_by_keyword(tokens, K_ONION_KEY); - if (!crypto_pk_public_exponent_ok(tok->key)) { - log_warn(LD_DIR, - "Relay's onion key had invalid exponent."); - goto next; - } - router_set_rsa_onion_pkey(tok->key, &md->onion_pkey, - &md->onion_pkey_len); - crypto_pk_free(tok->key); - - if ((tok = find_opt_by_keyword(tokens, K_ONION_KEY_NTOR))) { - curve25519_public_key_t k; - tor_assert(tok->n_args >= 1); - if (curve25519_public_from_base64(&k, tok->args[0]) < 0) { - log_warn(LD_DIR, "Bogus ntor-onion-key in microdesc"); - goto next; - } - md->onion_curve25519_pkey = - tor_memdup(&k, sizeof(curve25519_public_key_t)); - } - - smartlist_t *id_lines = find_all_by_keyword(tokens, K_ID); - if (id_lines) { - SMARTLIST_FOREACH_BEGIN(id_lines, directory_token_t *, t) { - tor_assert(t->n_args >= 2); - if (!strcmp(t->args[0], "ed25519")) { - if (md->ed25519_identity_pkey) { - log_warn(LD_DIR, "Extra ed25519 key in microdesc"); - smartlist_free(id_lines); - goto next; - } - ed25519_public_key_t k; - if (ed25519_public_from_base64(&k, t->args[1])<0) { - log_warn(LD_DIR, "Bogus ed25519 key in microdesc"); - smartlist_free(id_lines); - goto next; - } - md->ed25519_identity_pkey = tor_memdup(&k, sizeof(k)); - } - } SMARTLIST_FOREACH_END(t); - smartlist_free(id_lines); - } - - { - smartlist_t *a_lines = find_all_by_keyword(tokens, K_A); - if (a_lines) { - find_single_ipv6_orport(a_lines, &md->ipv6_addr, &md->ipv6_orport); - smartlist_free(a_lines); - } - } - - if ((tok = find_opt_by_keyword(tokens, K_FAMILY))) { - int i; - md->family = smartlist_new(); - for (i=0;in_args;++i) { - if (!is_legal_nickname_or_hexdigest(tok->args[i])) { - log_warn(LD_DIR, "Illegal nickname %s in family line", - escaped(tok->args[i])); - goto next; - } - smartlist_add_strdup(md->family, tok->args[i]); - } - } - - if ((tok = find_opt_by_keyword(tokens, K_P))) { - md->exit_policy = parse_short_policy(tok->args[0]); - } - if ((tok = find_opt_by_keyword(tokens, K_P6))) { - md->ipv6_exit_policy = parse_short_policy(tok->args[0]); - } - - smartlist_add(result, md); - okay = 1; - - md = NULL; - next: - if (! okay && invalid_digests_out) { - smartlist_add(invalid_digests_out, - tor_memdup(md->digest, DIGEST256_LEN)); - } - microdesc_free(md); - md = NULL; - - SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); - memarea_clear(area); - smartlist_clear(tokens); - s = start_of_next_microdesc; - } - - SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); - memarea_drop_all(area); - smartlist_free(tokens); - - return result; -} - /** Called on startup; right now we just handle scanning the unparseable * descriptor dumps, but hang anything else we might need to do in the * future here as well. diff --git a/src/feature/dirparse/routerparse.h b/src/feature/dirparse/routerparse.h index f90ad08a99..00020936e3 100644 --- a/src/feature/dirparse/routerparse.h +++ b/src/feature/dirparse/routerparse.h @@ -54,10 +54,9 @@ networkstatus_t *networkstatus_parse_vote_from_string(const char *s, ns_detached_signatures_t *networkstatus_parse_detached_signatures( const char *s, const char *eos); -smartlist_t *microdescs_parse_from_string(const char *s, const char *eos, - int allow_annotations, - saved_location_t where, - smartlist_t *invalid_digests_out); +int find_single_ipv6_orport(const smartlist_t *list, + tor_addr_t *addr_out, + uint16_t *port_out); void routerparse_init(void); void routerparse_free_all(void); diff --git a/src/feature/nodelist/microdesc.c b/src/feature/nodelist/microdesc.c index 85bc59812b..3716513f05 100644 --- a/src/feature/nodelist/microdesc.c +++ b/src/feature/nodelist/microdesc.c @@ -12,20 +12,21 @@ #include "lib/fdio/fdio.h" -#include "core/or/circuitbuild.h" #include "app/config/config.h" -#include "feature/dircommon/directory.h" +#include "core/or/circuitbuild.h" +#include "core/or/policies.h" +#include "feature/client/entrynodes.h" #include "feature/dircache/dirserv.h" #include "feature/dirclient/dlstatus.h" -#include "feature/client/entrynodes.h" +#include "feature/dircommon/directory.h" +#include "feature/dirparse/microdesc_parse.h" +#include "feature/dirparse/routerparse.h" +#include "feature/nodelist/dirlist.h" #include "feature/nodelist/microdesc.h" #include "feature/nodelist/networkstatus.h" #include "feature/nodelist/nodelist.h" -#include "core/or/policies.h" -#include "feature/relay/router.h" -#include "feature/nodelist/dirlist.h" #include "feature/nodelist/routerlist.h" -#include "feature/dirparse/routerparse.h" +#include "feature/relay/router.h" #include "feature/nodelist/microdesc_st.h" #include "feature/nodelist/networkstatus_st.h" diff --git a/src/test/fuzz/fuzz_microdesc.c b/src/test/fuzz/fuzz_microdesc.c index 789e522af6..ab54cf2a34 100644 --- a/src/test/fuzz/fuzz_microdesc.c +++ b/src/test/fuzz/fuzz_microdesc.c @@ -1,8 +1,8 @@ /* Copyright (c) 2016-2018, The Tor Project, Inc. */ /* See LICENSE for licensing information */ -#define ROUTERPARSE_PRIVATE + #include "core/or/or.h" -#include "feature/dirparse/routerparse.h" +#include "feature/dirparse/microdesc_parse.h" #include "feature/dirparse/unparseable.h" #include "feature/nodelist/microdesc.h" #include "lib/crypt_ops/crypto_ed25519.h" diff --git a/src/test/test_microdesc.c b/src/test/test_microdesc.c index bec8053f6e..8ede2690ec 100644 --- a/src/test/test_microdesc.c +++ b/src/test/test_microdesc.c @@ -4,13 +4,14 @@ #include "orconfig.h" #include "core/or/or.h" -#include "app/config/config.h" #define DIRVOTE_PRIVATE +#include "app/config/config.h" #include "feature/dirauth/dirvote.h" +#include "feature/dirparse/microdesc_parse.h" +#include "feature/dirparse/routerparse.h" #include "feature/nodelist/microdesc.h" #include "feature/nodelist/networkstatus.h" #include "feature/nodelist/routerlist.h" -#include "feature/dirparse/routerparse.h" #include "feature/nodelist/torcert.h" #include "feature/nodelist/microdesc_st.h"