From 9744a40f7aa71f874b9f3eeb3c5e7d0899c8d409 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Mon, 11 Jan 2016 11:13:04 +0100 Subject: [PATCH 01/26] Add tor_htonll/ntohll functions Signed-off-by: David Goulet --- src/common/util.c | 21 +++++++++++++++++++++ src/common/util.h | 2 ++ 2 files changed, 23 insertions(+) diff --git a/src/common/util.c b/src/common/util.c index 4b6df81b7d..3c5341d460 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -5587,3 +5587,24 @@ clamp_double_to_int64(double number) return signbit(number) ? INT64_MIN : INT64_MAX; } +/** Return a uint64_t value from a in network byte order. */ +uint64_t +tor_htonll(uint64_t a) +{ +#ifdef WORDS_BIGENDIAN + /* Big endian. */ + return a; +#else /* WORDS_BIGENDIAN */ + /* Little endian. The worst... */ + return htonl((uint32_t)(a>>32)) | + (((uint64_t)htonl((uint32_t)a))<<32); +#endif /* WORDS_BIGENDIAN */ +} + +/** Return a uint64_t value from a in host byte order. */ +uint64_t +tor_ntohll(uint64_t a) +{ + return tor_htonll(a); +} + diff --git a/src/common/util.h b/src/common/util.h index 7cb33dc680..fdf1c03b6e 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -61,6 +61,8 @@ void *tor_memdup_(const void *mem, size_t len DMALLOC_PARAMS) void *tor_memdup_nulterm_(const void *mem, size_t len DMALLOC_PARAMS) ATTR_MALLOC ATTR_NONNULL((1)); void tor_free_(void *mem); +uint64_t tor_htonll(uint64_t a); +uint64_t tor_ntohll(uint64_t a); #ifdef USE_DMALLOC extern int dmalloc_free(const char *file, const int line, void *pnt, const int func_id); From b3b4ffce2e23bfb6a2af374cd8bfa5981628a342 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 3 May 2016 10:18:45 -0400 Subject: [PATCH 02/26] prop250: Add memory and disk state in new files This commit introduces two new files with their header. "shared_random.c" contains basic functions to initialize the state and allow commit decoding for the disk state to be able to parse them from disk. "shared_random_state.c" contains everything that has to do with the state for both our memory and disk. Lots of helper functions as well as a mechanism to query the state in a synchronized way. Signed-off-by: David Goulet Signed-off-by: George Kadianakis --- src/or/dirvote.c | 88 +-- src/or/dirvote.h | 32 + src/or/include.am | 4 + src/or/shared_random.c | 286 +++++++++ src/or/shared_random.h | 114 ++++ src/or/shared_random_state.c | 1086 ++++++++++++++++++++++++++++++++++ src/or/shared_random_state.h | 126 ++++ 7 files changed, 1698 insertions(+), 38 deletions(-) create mode 100644 src/or/shared_random.c create mode 100644 src/or/shared_random.h create mode 100644 src/or/shared_random_state.c create mode 100644 src/or/shared_random_state.h diff --git a/src/or/dirvote.c b/src/or/dirvote.c index ad0696eddb..a08f6eceaa 100644 --- a/src/or/dirvote.c +++ b/src/or/dirvote.c @@ -2511,50 +2511,60 @@ dirvote_get_start_of_next_interval(time_t now, int interval, int offset) return next; } -/** Scheduling information for a voting interval. */ -static struct { - /** When do we generate and distribute our vote for this interval? */ - time_t voting_starts; - /** When do we send an HTTP request for any votes that we haven't - * been posted yet?*/ - time_t fetch_missing_votes; - /** When do we give up on getting more votes and generate a consensus? */ - time_t voting_ends; - /** When do we send an HTTP request for any signatures we're expecting to - * see on the consensus? */ - time_t fetch_missing_signatures; - /** When do we publish the consensus? */ - time_t interval_starts; +/* Using the time now, return the next voting valid-after time. */ +time_t +get_next_valid_after_time(time_t now) +{ + time_t next_valid_after_time; + const or_options_t *options = get_options(); + voting_schedule_t *new_voting_schedule = + get_voting_schedule(options, now, LOG_INFO); + tor_assert(new_voting_schedule); - /* True iff we have generated and distributed our vote. */ - int have_voted; - /* True iff we've requested missing votes. */ - int have_fetched_missing_votes; - /* True iff we have built a consensus and sent the signatures around. */ - int have_built_consensus; - /* True iff we've fetched missing signatures. */ - int have_fetched_missing_signatures; - /* True iff we have published our consensus. */ - int have_published_consensus; -} voting_schedule = {0,0,0,0,0,0,0,0,0,0}; + next_valid_after_time = new_voting_schedule->interval_starts; + tor_free(new_voting_schedule); + + return next_valid_after_time; +} + +static voting_schedule_t voting_schedule = {0}; /** Set voting_schedule to hold the timing for the next vote we should be * doing. */ void dirvote_recalculate_timing(const or_options_t *options, time_t now) +{ + voting_schedule_t *new_voting_schedule; + + if (!authdir_mode_v3(options)) { + return; + } + + /* get the new voting schedule */ + new_voting_schedule = get_voting_schedule(options, now, LOG_NOTICE); + tor_assert(new_voting_schedule); + + /* Fill in the global static struct now */ + memcpy(&voting_schedule, new_voting_schedule, sizeof(voting_schedule)); + tor_free(new_voting_schedule); +} + +/* Populate and return a new voting_schedule_t that can be used to schedule + * voting. The object is allocated on the heap and it's the responsibility of + * the caller to free it. Can't fail. */ +voting_schedule_t * +get_voting_schedule(const or_options_t *options, time_t now, int severity) { int interval, vote_delay, dist_delay; time_t start; time_t end; networkstatus_t *consensus; + voting_schedule_t *new_voting_schedule; - if (!authdir_mode_v3(options)) - return; + new_voting_schedule = tor_malloc_zero(sizeof(voting_schedule_t)); consensus = networkstatus_get_live_consensus(now); - memset(&voting_schedule, 0, sizeof(voting_schedule)); - if (consensus) { interval = (int)( consensus->fresh_until - consensus->valid_after ); vote_delay = consensus->vote_seconds; @@ -2570,7 +2580,7 @@ dirvote_recalculate_timing(const or_options_t *options, time_t now) if (vote_delay + dist_delay > interval/2) vote_delay = dist_delay = interval / 4; - start = voting_schedule.interval_starts = + start = new_voting_schedule->interval_starts = dirvote_get_start_of_next_interval(now,interval, options->TestingV3AuthVotingStartOffset); end = dirvote_get_start_of_next_interval(start+1, interval, @@ -2578,18 +2588,20 @@ dirvote_recalculate_timing(const or_options_t *options, time_t now) tor_assert(end > start); - voting_schedule.fetch_missing_signatures = start - (dist_delay/2); - voting_schedule.voting_ends = start - dist_delay; - voting_schedule.fetch_missing_votes = start - dist_delay - (vote_delay/2); - voting_schedule.voting_starts = start - dist_delay - vote_delay; + new_voting_schedule->fetch_missing_signatures = start - (dist_delay/2); + new_voting_schedule->voting_ends = start - dist_delay; + new_voting_schedule->fetch_missing_votes = start - dist_delay - (vote_delay/2); + new_voting_schedule->voting_starts = start - dist_delay - vote_delay; { char tbuf[ISO_TIME_LEN+1]; - format_iso_time(tbuf, voting_schedule.interval_starts); - log_notice(LD_DIR,"Choosing expected valid-after time as %s: " - "consensus_set=%d, interval=%d", - tbuf, consensus?1:0, interval); + format_iso_time(tbuf, new_voting_schedule->interval_starts); + tor_log(severity, LD_DIR,"Choosing expected valid-after time as %s: " + "consensus_set=%d, interval=%d", + tbuf, consensus?1:0, interval); } + + return new_voting_schedule; } /** Entry point: Take whatever voting actions are pending as of now. */ diff --git a/src/or/dirvote.h b/src/or/dirvote.h index 0b1d284060..f2080a522e 100644 --- a/src/or/dirvote.h +++ b/src/or/dirvote.h @@ -121,12 +121,44 @@ void ns_detached_signatures_free(ns_detached_signatures_t *s); authority_cert_t *authority_cert_dup(authority_cert_t *cert); /* vote scheduling */ + +/** Scheduling information for a voting interval. */ +typedef struct { + /** When do we generate and distribute our vote for this interval? */ + time_t voting_starts; + /** When do we send an HTTP request for any votes that we haven't + * been posted yet?*/ + time_t fetch_missing_votes; + /** When do we give up on getting more votes and generate a consensus? */ + time_t voting_ends; + /** When do we send an HTTP request for any signatures we're expecting to + * see on the consensus? */ + time_t fetch_missing_signatures; + /** When do we publish the consensus? */ + time_t interval_starts; + + /* True iff we have generated and distributed our vote. */ + int have_voted; + /* True iff we've requested missing votes. */ + int have_fetched_missing_votes; + /* True iff we have built a consensus and sent the signatures around. */ + int have_built_consensus; + /* True iff we've fetched missing signatures. */ + int have_fetched_missing_signatures; + /* True iff we have published our consensus. */ + int have_published_consensus; +} voting_schedule_t; + +voting_schedule_t *get_voting_schedule(const or_options_t *options, + time_t now, int severity); + void dirvote_get_preferred_voting_intervals(vote_timing_t *timing_out); time_t dirvote_get_start_of_next_interval(time_t now, int interval, int offset); void dirvote_recalculate_timing(const or_options_t *options, time_t now); void dirvote_act(const or_options_t *options, time_t now); +time_t get_next_valid_after_time(time_t now); /* invoked on timers and by outside triggers. */ struct pending_vote_t * dirvote_add_vote(const char *vote_body, diff --git a/src/or/include.am b/src/or/include.am index 19f1a7fe0a..744a507402 100644 --- a/src/or/include.am +++ b/src/or/include.am @@ -62,6 +62,8 @@ LIBTOR_A_SOURCES = \ src/or/onion.c \ src/or/onion_fast.c \ src/or/onion_tap.c \ + src/or/shared_random.c \ + src/or/shared_random_state.c \ src/or/transports.c \ src/or/periodic.c \ src/or/policies.c \ @@ -173,6 +175,8 @@ ORHEADERS = \ src/or/onion_ntor.h \ src/or/onion_tap.h \ src/or/or.h \ + src/or/shared_random.h \ + src/or/shared_random_state.h \ src/or/transports.h \ src/or/periodic.h \ src/or/policies.h \ diff --git a/src/or/shared_random.c b/src/or/shared_random.c new file mode 100644 index 0000000000..447ab27f44 --- /dev/null +++ b/src/or/shared_random.c @@ -0,0 +1,286 @@ +/* Copyright (c) 2016, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file shared_random.c + * + * \brief Functions and data structure needed to accomplish the shared + * random protocol as defined in proposal #250. + **/ + +#define SHARED_RANDOM_PRIVATE + +#include "or.h" +#include "shared_random.h" +#include "config.h" +#include "confparse.h" +#include "networkstatus.h" +#include "routerkeys.h" +#include "router.h" +#include "routerlist.h" +#include "shared_random_state.h" + +/* Allocate a new commit object and initializing it with identity + * that MUST be provided. The digest algorithm is set to the default one + * that is supported. The rest is uninitialized. This never returns NULL. */ +static sr_commit_t * +commit_new(const char *rsa_identity_fpr) +{ + sr_commit_t *commit; + + tor_assert(rsa_identity_fpr); + + commit = tor_malloc_zero(sizeof(*commit)); + commit->alg = SR_DIGEST_ALG; + strlcpy(commit->rsa_identity_fpr, rsa_identity_fpr, + sizeof(commit->rsa_identity_fpr)); + return commit; +} + +/* Parse the encoded commit. The format is: + * base64-encode( TIMESTAMP || H(REVEAL) ) + * + * If successfully decoded and parsed, commit is updated and 0 is returned. + * On error, return -1. */ +STATIC int +commit_decode(const char *encoded, sr_commit_t *commit) +{ + int decoded_len = 0; + size_t offset = 0; + /* XXX: Needs two extra bytes for the base64 decode calculation matches + * the binary length once decoded. #17868. */ + char b64_decoded[SR_COMMIT_LEN + 2]; + + tor_assert(encoded); + tor_assert(commit); + + if (strlen(encoded) > SR_COMMIT_BASE64_LEN) { + /* This means that if we base64 decode successfully the reveiced commit, + * we'll end up with a bigger decoded commit thus unusable. */ + goto error; + } + + /* Decode our encoded commit. Let's be careful here since _encoded_ is + * coming from the network in a dirauth vote so we expect nothing more + * than the base64 encoded length of a commit. */ + decoded_len = base64_decode(b64_decoded, sizeof(b64_decoded), + encoded, strlen(encoded)); + if (decoded_len < 0) { + log_warn(LD_BUG, "SR: Commit from authority %s can't be decoded.", + commit->rsa_identity_fpr); + goto error; + } + + if (decoded_len != SR_COMMIT_LEN) { + log_warn(LD_BUG, "SR: Commit from authority %s decoded length doesn't " + "match the expected length (%d vs %d).", + commit->rsa_identity_fpr, decoded_len, SR_COMMIT_LEN); + goto error; + } + + /* First is the timestamp (8 bytes). */ + commit->commit_ts = (time_t) tor_ntohll(get_uint64(b64_decoded)); + offset += sizeof(uint64_t); + /* Next is hashed reveal. */ + memcpy(commit->hashed_reveal, b64_decoded + offset, + sizeof(commit->hashed_reveal)); + /* Copy the base64 blob to the commit. Useful for voting. */ + strlcpy(commit->encoded_commit, encoded, sizeof(commit->encoded_commit)); + + return 0; + + error: + return -1; +} + +/* Parse the b64 blob at encoded containing reveal information and + * store the information in-place in commit. Return 0 on success else + * a negative value. */ +STATIC int +reveal_decode(const char *encoded, sr_commit_t *commit) +{ + int decoded_len = 0; + /* XXX: Needs two extra bytes for the base64 decode calculation matches + * the binary length once decoded. #17868. */ + char b64_decoded[SR_REVEAL_LEN + 2]; + + tor_assert(encoded); + tor_assert(commit); + + if (strlen(encoded) > SR_REVEAL_BASE64_LEN) { + /* This means that if we base64 decode successfully the received reveal + * value, we'll end up with a bigger decoded value thus unusable. */ + goto error; + } + + /* Decode our encoded reveal. Let's be careful here since _encoded_ is + * coming from the network in a dirauth vote so we expect nothing more + * than the base64 encoded length of our reveal. */ + decoded_len = base64_decode(b64_decoded, sizeof(b64_decoded), + encoded, strlen(encoded)); + if (decoded_len < 0) { + log_warn(LD_BUG, "SR: Reveal from authority %s can't be decoded.", + commit->rsa_identity_fpr); + goto error; + } + + if (decoded_len != SR_REVEAL_LEN) { + log_warn(LD_BUG, "SR: Reveal from authority %s decoded length is " + "doesn't match the expected length (%d vs %d)", + commit->rsa_identity_fpr, decoded_len, SR_REVEAL_LEN); + goto error; + } + + commit->reveal_ts = (time_t) tor_ntohll(get_uint64(b64_decoded)); + /* Copy the last part, the random value. */ + memcpy(commit->random_number, b64_decoded + 8, + sizeof(commit->random_number)); + /* Also copy the whole message to use during verification */ + strlcpy(commit->encoded_reveal, encoded, sizeof(commit->encoded_reveal)); + + return 0; + + error: + return -1; +} + +/* Cleanup both our global state and disk state. */ +static void +sr_cleanup(void) +{ + sr_state_free(); +} + +/* Free a commit object. */ +void +sr_commit_free(sr_commit_t *commit) +{ + if (commit == NULL) { + return; + } + /* Make sure we do not leave OUR random number in memory. */ + memwipe(commit->random_number, 0, sizeof(commit->random_number)); + tor_free(commit); +} + +/* Parse a list of arguments from a SRV value either from a vote, consensus + * or from our disk state and return a newly allocated srv object. NULL is + * returned on error. + * + * The arguments' order: + * num_reveals, value + */ +sr_srv_t * +sr_parse_srv(const smartlist_t *args) +{ + char *value; + int num_reveals, ok; + sr_srv_t *srv = NULL; + + tor_assert(args); + + if (smartlist_len(args) < 2) { + goto end; + } + + /* First argument is the number of reveal values */ + num_reveals = (int)tor_parse_long(smartlist_get(args, 0), + 10, 0, INT32_MAX, &ok, NULL); + if (!ok) { + goto end; + } + srv = tor_malloc_zero(sizeof(*srv)); + srv->num_reveals = num_reveals; + + /* Second and last argument is the shared random value it self. */ + value = smartlist_get(args, 1); + base16_decode((char *) srv->value, sizeof(srv->value), value, + HEX_DIGEST256_LEN); + end: + return srv; +} + +/* Parse a commit from a vote or from our disk state and return a newly + * allocated commit object. NULL is returned on error. + * + * The commit's data is in args and the order matters very much: + * algname, RSA fingerprint, commit value[, reveal value] + */ +sr_commit_t * +sr_parse_commit(const smartlist_t *args) +{ + char *value; + digest_algorithm_t alg; + const char *rsa_identity_fpr; + sr_commit_t *commit = NULL; + + if (smartlist_len(args) < 3) { + goto error; + } + + /* First argument is the algorithm. */ + value = smartlist_get(args, 0); + alg = crypto_digest_algorithm_parse_name(value); + if (alg != SR_DIGEST_ALG) { + log_warn(LD_BUG, "SR: Commit algorithm %s is not recognized.", + escaped(value)); + goto error; + } + + /* Second argument is the RSA fingerprint of the auth */ + rsa_identity_fpr = smartlist_get(args, 1); + if (base16_decode(digest, DIGEST_LEN, rsa_identity_fpr, + HEX_DIGEST_LEN) < 0) { + log_warn(LD_DIR, "SR: RSA fingerprint '%s' not decodable", + rsa_identity_fpr); + goto error; + } + /* Let's make sure, for extra safety, that this fingerprint is known to + * us. Even though this comes from a vote, doesn't hurt to be + * extracareful. */ + if (trusteddirserver_get_by_v3_auth_digest(digest) == NULL) { + log_warn(LD_DIR, "SR: Fingerprint %s is not from a recognized " + "authority. Discarding commit.", + rsa_identity_fpr); + goto error; + } + + /* Allocate commit since we have a valid identity now. */ + commit = commit_new(rsa_identity_fpr); + + /* Third argument is the commitment value base64-encoded. */ + value = smartlist_get(args, 2); + if (commit_decode(value, commit) < 0) { + goto error; + } + + /* (Optional) Fourth argument is the revealed value. */ + if (smartlist_len(args) > 3) { + value = smartlist_get(args, 3); + if (reveal_decode(value, commit) < 0) { + goto error; + } + } + + return commit; + + error: + sr_commit_free(commit); + return NULL; +} + +/* Initialize shared random subsystem. This MUST be called early in the boot + * process of tor. Return 0 on success else -1 on error. */ +int +sr_init(int save_to_disk) +{ + return sr_state_init(save_to_disk, 1); +} + +/* Save our state to disk and cleanup everything. */ +void +sr_save_and_cleanup(void) +{ + sr_state_save(); + sr_cleanup(); +} diff --git a/src/or/shared_random.h b/src/or/shared_random.h new file mode 100644 index 0000000000..447de0c4b5 --- /dev/null +++ b/src/or/shared_random.h @@ -0,0 +1,114 @@ +/* Copyright (c) 2016, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_SHARED_RANDOM_H +#define TOR_SHARED_RANDOM_H + +/* + * This file contains ABI/API of the shared random protocol defined in + * proposal #250. Every public functions and data structure are namespaced + * with "sr_" which stands for shared random. + */ + +#include "or.h" + +/* Protocol version */ +#define SR_PROTO_VERSION 1 +/* Default digest algorithm. */ +#define SR_DIGEST_ALG DIGEST_SHA3_256 +/* Invariant token in the SRV calculation. */ +#define SR_SRV_TOKEN "shared-random" +/* Don't count the NUL terminated byte even though the TOKEN has it. */ +#define SR_SRV_TOKEN_LEN (sizeof(SR_SRV_TOKEN) - 1) + +/* Length of the random number (in bytes). */ +#define SR_RANDOM_NUMBER_LEN 32 +/* Size of a decoded commit value in a vote or state. It's a hash and a + * timestamp. It adds up to 40 bytes. */ +#define SR_COMMIT_LEN (sizeof(uint64_t) + DIGEST256_LEN) +/* Size of a decoded reveal value from a vote or state. It's a 64 bit + * timestamp and the hashed random number. This adds up to 40 bytes. */ +#define SR_REVEAL_LEN (sizeof(uint64_t) + DIGEST256_LEN) +/* Size of SRV message length. The construction is has follow: + * "shared-random" | INT_8(reveal_num) | INT_8(version) | PREV_SRV */ +#define SR_SRV_MSG_LEN \ + (SR_SRV_TOKEN_LEN + 1 + 1 + DIGEST256_LEN) + +/* Length of base64 encoded commit NOT including the NULL terminated byte. + * Formula is taken from base64_encode_size. */ +#define SR_COMMIT_BASE64_LEN \ + (((SR_COMMIT_LEN - 1) / 3) * 4 + 4) +/* Length of base64 encoded reveal NOT including the NULL terminated byte. + * Formula is taken from base64_encode_size. This adds up to 56 bytes. */ +#define SR_REVEAL_BASE64_LEN \ + (((SR_REVEAL_LEN - 1) / 3) * 4 + 4) + +/* Protocol phase. */ +typedef enum { + /* Commitment phase */ + SR_PHASE_COMMIT = 1, + /* Reveal phase */ + SR_PHASE_REVEAL = 2, +} sr_phase_t; + +/* A shared random value (SRV). */ +typedef struct sr_srv_t { + /* The number of reveal values used to derive this SRV. */ + int num_reveals; + /* The actual value. This is the stored result of SHA3-256. */ + uint8_t value[DIGEST256_LEN]; +} sr_srv_t; + +/* A commit (either ours or from another authority). */ +typedef struct sr_commit_t { + /* Hashing algorithm used. */ + digest_algorithm_t alg; + + /* Commit owner info */ + + /* The RSA identity fingerprint of the authority. */ + char rsa_identity_fpr[FINGERPRINT_LEN + 1]; + + /* Commitment information */ + + /* Timestamp of reveal. Correspond to TIMESTAMP. */ + time_t reveal_ts; + /* H(REVEAL) as found in COMMIT message. */ + char hashed_reveal[DIGEST256_LEN]; + /* Base64 encoded COMMIT. We use this to put it in our vote. */ + char encoded_commit[SR_COMMIT_BASE64_LEN + 1]; + + /* Reveal information */ + + /* H(RN) which is what we used as the random value for this commit. We + * don't use the raw bytes since those are sent on the network thus + * avoiding possible information leaks of our PRNG. */ + uint8_t random_number[SR_RANDOM_NUMBER_LEN]; + /* Timestamp of commit. Correspond to TIMESTAMP. */ + time_t commit_ts; + /* This is the whole reveal message. We use it during verification */ + char encoded_reveal[SR_REVEAL_BASE64_LEN + 1]; +} sr_commit_t; + +/* API */ + +/* Public methods: */ + +int sr_init(int save_to_disk); +void sr_save_and_cleanup(void); +void sr_commit_free(sr_commit_t *commit); + +/* Private methods (only used by shared_random_state.c): */ + +sr_commit_t *sr_parse_commit(const smartlist_t *args); +sr_srv_t *sr_parse_srv(const smartlist_t *args); + +#ifdef SHARED_RANDOM_PRIVATE + +/* Decode. */ +STATIC int commit_decode(const char *encoded, sr_commit_t *commit); +STATIC int reveal_decode(const char *encoded, sr_commit_t *commit); + +#endif /* SHARED_RANDOM_PRIVATE */ + +#endif /* TOR_SHARED_RANDOM_H */ diff --git a/src/or/shared_random_state.c b/src/or/shared_random_state.c new file mode 100644 index 0000000000..9fd3b27546 --- /dev/null +++ b/src/or/shared_random_state.c @@ -0,0 +1,1086 @@ +/* Copyright (c) 2016, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file shared_random_state.c + * + * \brief Functions and data structures for the state of the random protocol + * as defined in proposal #250. + **/ + +#define SHARED_RANDOM_STATE_PRIVATE + +#include "or.h" +#include "shared_random.h" +#include "config.h" +#include "confparse.h" +#include "dirvote.h" +#include "networkstatus.h" +#include "router.h" +#include "shared_random_state.h" + +/* Default filename of the shared random state on disk. */ +static const char default_fname[] = "sr-state"; + +/* Our shared random protocol state. There is only one possible state per + * protocol run so this is the global state which is reset at every run once + * the shared random value has been computed. */ +static sr_state_t *sr_state = NULL; + +/* Representation of our persistent state on disk. The sr_state above + * contains the data parsed from this state. When we save to disk, we + * translate the sr_state to this sr_disk_state. */ +static sr_disk_state_t *sr_disk_state = NULL; + +/* Disk state file keys. */ +static const char dstate_commit_key[] = "Commit"; +static const char dstate_prev_srv_key[] = "SharedRandPreviousValue"; +static const char dstate_cur_srv_key[] = "SharedRandCurrentValue"; + +/* These next two are duplicates or near-duplicates from config.c */ +#define VAR(name, conftype, member, initvalue) \ + { name, CONFIG_TYPE_ ## conftype, STRUCT_OFFSET(sr_disk_state_t, member), \ + initvalue } +/* As VAR, but the option name and member name are the same. */ +#define V(member, conftype, initvalue) \ + VAR(#member, conftype, member, initvalue) +/* Our persistent state magic number. */ +#define SR_DISK_STATE_MAGIC 0x98AB1254 +/* Each protocol phase has 12 rounds */ +#define SHARED_RANDOM_N_ROUNDS 12 +/* Number of phase we have in a protocol. */ +#define SHARED_RANDOM_N_PHASES 2 + +static int +disk_state_validate_cb(void *old_state, void *state, void *default_state, + int from_setconf, char **msg); + +/* Array of variables that are saved to disk as a persistent state. */ +static config_var_t state_vars[] = { + V(Version, INT, "0"), + V(TorVersion, STRING, NULL), + V(ValidAfter, ISOTIME, NULL), + V(ValidUntil, ISOTIME, NULL), + + V(Commit, LINELIST, NULL), + + V(SharedRandValues, LINELIST_V, NULL), + VAR("SharedRandPreviousValue",LINELIST_S, SharedRandValues, NULL), + VAR("SharedRandCurrentValue", LINELIST_S, SharedRandValues, NULL), + { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } +}; + +/* "Extra" variable in the state that receives lines we can't parse. This + * lets us preserve options from versions of Tor newer than us. */ +static config_var_t state_extra_var = { + "__extra", CONFIG_TYPE_LINELIST, + STRUCT_OFFSET(sr_disk_state_t, ExtraLines), NULL +}; + +/* Configuration format of sr_disk_state_t. */ +static const config_format_t state_format = { + sizeof(sr_disk_state_t), + SR_DISK_STATE_MAGIC, + STRUCT_OFFSET(sr_disk_state_t, magic_), + NULL, + state_vars, + disk_state_validate_cb, + &state_extra_var, +}; + +/* Return the voting interval of the tor vote subsystem. */ +static int +get_voting_interval(void) +{ + int interval; + networkstatus_t *consensus = networkstatus_get_live_consensus(time(NULL)); + + if (consensus) { + interval = (int)(consensus->fresh_until - consensus->valid_after); + } else { + /* Same for both a testing and real network. We voluntarily ignore the + * InitialVotingInterval since it complexifies things and it doesn't + * affect the SR protocol. */ + interval = get_options()->V3AuthVotingInterval; + } + tor_assert(interval > 0); + return interval; +} + +/* Given the time now, return the start time of the current round of + * the SR protocol. For example, if it's 23:47:08, the current round thus + * started at 23:47:00 for a voting interval of 10 seconds. */ +static time_t +get_start_time_of_current_round(time_t now) +{ + const or_options_t *options = get_options(); + int voting_interval = get_voting_interval(); + voting_schedule_t *new_voting_schedule = + get_voting_schedule(options, now, LOG_INFO); + tor_assert(new_voting_schedule); + + /* First, get the start time of the next round */ + time_t next_start = new_voting_schedule->interval_starts; + /* Now roll back next_start by a voting interval to find the start time of + the current round. */ + time_t curr_start = dirvote_get_start_of_next_interval( + next_start - voting_interval - 1, + voting_interval, + options->TestingV3AuthVotingStartOffset); + + tor_free(new_voting_schedule); + + return curr_start; +} + +/* Return the time we should expire the state file created at now. + * We expire the state file in the beginning of the next protocol run. */ +STATIC time_t +get_state_valid_until_time(time_t now) +{ + int total_rounds = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES; + int current_round, voting_interval, rounds_left; + time_t valid_until, beginning_of_current_round; + + voting_interval = get_voting_interval(); + /* Find the time the current round started. */ + beginning_of_current_round = get_start_time_of_current_round(now); + + /* Find how many rounds are left till the end of the protocol run */ + current_round = (now / voting_interval) % total_rounds; + rounds_left = total_rounds - current_round; + + /* To find the valid-until time now, take the start time of the current + * round and add to it the time it takes for the leftover rounds to + * complete. */ + valid_until = beginning_of_current_round + (rounds_left * voting_interval); + + { /* Logging */ + char tbuf[ISO_TIME_LEN + 1]; + format_iso_time(tbuf, valid_until); + log_debug(LD_DIR, "SR: Valid until time for state set to %s.", tbuf); + } + + return valid_until; +} + +/* Given the consensus 'valid-after' time, return the protocol phase we should + * be in. */ +STATIC sr_phase_t +get_sr_protocol_phase(time_t valid_after) +{ + /* Shared random protocol has two phases, commit and reveal. */ + int total_periods = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES; + int current_slot; + + /* Split time into slots of size 'voting_interval'. See which slot we are + * currently into, and find which phase it corresponds to. */ + current_slot = (valid_after / get_voting_interval()) % total_periods; + + if (current_slot < SHARED_RANDOM_N_ROUNDS) { + return SR_PHASE_COMMIT; + } else { + return SR_PHASE_REVEAL; + } +} + +/* Add the given commit to state. It MUST be a valid commit + * and there shouldn't be a commit from the same authority in the state + * already else verification hasn't been done prior. This takes ownership of + * the commit once in our state. */ +static void +commit_add_to_state(sr_commit_t *commit, sr_state_t *state) +{ + sr_commit_t *saved_commit; + + tor_assert(commit); + tor_assert(state); + + saved_commit = digestmap_set(state->commits, commit->rsa_identity_fpr, + commit); + if (saved_commit != NULL) { + /* This means we already have that commit in our state so adding twice + * the same commit is either a code flow error, a corrupted disk state + * or some new unknown issue. */ + log_warn(LD_DIR, "SR: Commit from %s exists in our state while " + "adding it: '%s'", commit->rsa_identity_fpr, + commit->encoded_commit); + sr_commit_free(saved_commit); + } +} + +/* Helper: deallocate a commit object. (Used with digestmap_free(), which + * requires a function pointer whose argument is void *). */ +static void +commit_free_(void *p) +{ + sr_commit_free(p); +} + +/* Free a state that was allocated with state_new(). */ +static void +state_free(sr_state_t *state) +{ + if (state == NULL) { + return; + } + tor_free(state->fname); + digestmap_free(state->commits, commit_free_); + tor_free(state->current_srv); + tor_free(state->previous_srv); + tor_free(state); +} + +/* Allocate an sr_state_t object and returns it. If no fname, the + * default file name is used. This function does NOT initialize the state + * timestamp, phase or shared random value. NULL is never returned. */ +static sr_state_t * +state_new(const char *fname, time_t now) +{ + sr_state_t *new_state = tor_malloc_zero(sizeof(*new_state)); + /* If file name is not provided, use default. */ + if (fname == NULL) { + fname = default_fname; + } + new_state->fname = tor_strdup(fname); + new_state->version = SR_PROTO_VERSION; + new_state->commits = digestmap_new(); + new_state->phase = get_sr_protocol_phase(now); + new_state->valid_until = get_state_valid_until_time(now); + return new_state; +} + +/* Set our global state pointer with the one given. */ +static void +state_set(sr_state_t *state) +{ + tor_assert(state); + if (sr_state != NULL) { + state_free(sr_state); + } + sr_state = state; +} + +/* Free an allocated disk state. */ +static void +disk_state_free(sr_disk_state_t *state) +{ + if (state == NULL) { + return; + } + config_free(&state_format, state); +} + +/* Allocate a new disk state, initialize it and return it. */ +static sr_disk_state_t * +disk_state_new(time_t now) +{ + sr_disk_state_t *new_state = tor_malloc_zero(sizeof(*new_state)); + + new_state->magic_ = SR_DISK_STATE_MAGIC; + new_state->Version = SR_PROTO_VERSION; + new_state->TorVersion = tor_strdup(get_version()); + new_state->ValidUntil = get_state_valid_until_time(now); + new_state->ValidAfter = now; + + /* Init config format. */ + config_init(&state_format, new_state); + return new_state; +} + +/* Set our global disk state with the given state. */ +static void +disk_state_set(sr_disk_state_t *state) +{ + tor_assert(state); + if (sr_disk_state != NULL) { + disk_state_free(sr_disk_state); + } + sr_disk_state = state; +} + +/* Return -1 if the disk state is invalid (something in there that we can't or + * shouldn't use). Return 0 if everything checks out. */ +static int +disk_state_validate(const sr_disk_state_t *state) +{ + time_t now; + + tor_assert(state); + + /* Do we support the protocol version in the state or is it 0 meaning + * Version wasn't found in the state file or bad anyway ? */ + if (state->Version == 0 || state->Version > SR_PROTO_VERSION) { + goto invalid; + } + + /* If the valid until time is before now, we shouldn't use that state. */ + now = time(NULL); + if (state->ValidUntil < now) { + log_info(LD_DIR, "SR: Disk state has expired. Ignoring it."); + goto invalid; + } + + /* Make sure we don't have a valid after time that is earlier than a valid + * until time which would make things not work well. */ + if (state->ValidAfter >= state->ValidUntil) { + log_info(LD_DIR, "SR: Disk state valid after/until times are invalid."); + goto invalid; + } + + return 0; + + invalid: + return -1; +} + +/* Validate the disk state (NOP for now). */ +static int +disk_state_validate_cb(void *old_state, void *state, void *default_state, + int from_setconf, char **msg) +{ + /* We don't use these; only options do. */ + (void) from_setconf; + (void) default_state; + (void) old_state; + + /* This is called by config_dump which is just before we are about to + * write it to disk. At that point, our global memory state has been + * copied to the disk state so it's fair to assume it's trustable. */ + (void) state; + (void) msg; + return 0; +} + +/* Parse the Commit line(s) in the disk state and translate them to the + * the memory state. Return 0 on success else -1 on error. */ +static int +disk_state_parse_commits(sr_state_t *state, + const sr_disk_state_t *disk_state) +{ + config_line_t *line; + smartlist_t *args = NULL; + + tor_assert(state); + tor_assert(disk_state); + + for (line = disk_state->Commit; line; line = line->next) { + sr_commit_t *commit = NULL; + + /* Extra safety. */ + if (strcasecmp(line->key, dstate_commit_key) || + line->value == NULL) { + /* Ignore any lines that are not commits. */ + tor_fragile_assert(); + continue; + } + args = smartlist_new(); + smartlist_split_string(args, line->value, " ", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + if (smartlist_len(args) < 3) { + log_warn(LD_BUG, "SR: Too few arguments in Commit Line: %s", + escaped(line->value)); + goto error; + } + commit = sr_parse_commit(args); + if (commit == NULL) { + /* Ignore badly formed commit. It could also be a authority + * fingerprint that we don't know about so it shouldn't be used. */ + continue; + } + /* Add commit to our state pointer. */ + commit_add_to_state(commit, state); + + SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); + smartlist_free(args); + } + + return 0; + + error: + SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); + smartlist_free(args); + return -1; +} + +/* Parse a share random value line from the disk state and save it to dst + * which is an allocated srv object. Return 0 on success else -1. */ +static int +disk_state_parse_srv(const char *value, sr_srv_t *dst) +{ + int ret = -1; + smartlist_t *args; + sr_srv_t *srv; + + tor_assert(value); + tor_assert(dst); + + args = smartlist_new(); + smartlist_split_string(args, value, " ", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + if (smartlist_len(args) < 2) { + log_warn(LD_BUG, "SR: Too few arguments in shared random value. " + "Line: %s", escaped(value)); + goto error; + } + srv = sr_parse_srv(args); + if (srv == NULL) { + goto error; + } + dst->num_reveals = srv->num_reveals; + memcpy(dst->value, srv->value, sizeof(dst->value)); + tor_free(srv); + ret = 0; + + error: + SMARTLIST_FOREACH(args, char *, s, tor_free(s)); + smartlist_free(args); + return ret; +} + +/* Parse both SharedRandCurrentValue and SharedRandPreviousValue line from + * the state. Return 0 on success else -1. */ +static int +disk_state_parse_sr_values(sr_state_t *state, + const sr_disk_state_t *disk_state) +{ + /* Only one value per type (current or previous) is allowed so we keep + * track of it with these flag. */ + unsigned int seen_previous = 0, seen_current = 0; + config_line_t *line; + sr_srv_t *srv = NULL; + + tor_assert(state); + tor_assert(disk_state); + + for (line = disk_state->SharedRandValues; line; line = line->next) { + if (line->value == NULL) { + continue; + } + srv = tor_malloc_zero(sizeof(*srv)); + if (disk_state_parse_srv(line->value, srv) < 0) { + log_warn(LD_BUG, "SR: Broken current SRV line in state %s", + escaped(line->value)); + goto bad; + } + if (!strcasecmp(line->key, dstate_prev_srv_key)) { + if (seen_previous) { + log_warn(LD_DIR, "SR: Second previous SRV value seen. Bad state"); + goto bad; + } + state->previous_srv = srv; + seen_previous = 1; + } else if (!strcasecmp(line->key, dstate_cur_srv_key)) { + if (seen_current) { + log_warn(LD_DIR, "SR: Second current SRV value seen. Bad state"); + goto bad; + } + state->current_srv = srv; + seen_current = 1; + } else { + /* Unknown key. Ignoring. */ + tor_free(srv); + } + } + + return 0; + bad: + tor_free(srv); + return -1; +} + +/* Parse the given disk state and set a newly allocated state. On success, + * return that state else NULL. */ +static sr_state_t * +disk_state_parse(const sr_disk_state_t *new_disk_state) +{ + sr_state_t *new_state = state_new(default_fname, time(NULL)); + + tor_assert(new_disk_state); + + new_state->version = new_disk_state->Version; + new_state->valid_until = new_disk_state->ValidUntil; + new_state->valid_after = new_disk_state->ValidAfter; + + /* Set our current phase according to the valid-after time in our disk + * state. The disk state we are parsing contains everything for the phase + * starting at valid_after so make sure our phase reflects that. */ + new_state->phase = get_sr_protocol_phase(new_state->valid_after); + + /* Parse the shared random values. */ + if (disk_state_parse_sr_values(new_state, new_disk_state) < 0) { + goto error; + } + /* Parse the commits. */ + if (disk_state_parse_commits(new_state, new_disk_state) < 0) { + goto error; + } + /* Great! This new state contains everything we had on disk. */ + return new_state; + + error: + state_free(new_state); + return NULL; +} + +/* From a valid commit object and an allocated config line, set the line's + * value to the state string representation of a commit. */ +static void +disk_state_put_commit_line(const sr_commit_t *commit, config_line_t *line) +{ + char *reveal_str = NULL; + + tor_assert(commit); + tor_assert(line); + + if (!tor_mem_is_zero(commit->encoded_reveal, + sizeof(commit->encoded_reveal))) { + /* Add extra whitespace so we can format the line correctly. */ + tor_asprintf(&reveal_str, " %s", commit->encoded_reveal); + } + tor_asprintf(&line->value, "%s %s %s%s", + crypto_digest_algorithm_get_name(commit->alg), + commit->rsa_identity_fpr, + commit->encoded_commit, + reveal_str != NULL ? reveal_str : ""); + if (reveal_str != NULL) { + memwipe(reveal_str, 0, strlen(reveal_str)); + tor_free(reveal_str); + } +} + +/* From a valid srv object and an allocated config line, set the line's + * value to the state string representation of a shared random value. */ +static void +disk_state_put_srv_line(const sr_srv_t *srv, config_line_t *line) +{ + char encoded[HEX_DIGEST256_LEN + 1]; + + tor_assert(line); + + /* No SRV value thus don't add the line. This is possible since we might + * not have a current or previous SRV value in our state. */ + if (srv == NULL) { + return; + } + base16_encode(encoded, sizeof(encoded), (const char *) srv->value, + sizeof(srv->value)); + tor_asprintf(&line->value, "%d %s", srv->num_reveals, encoded); +} + +/* Reset disk state that is free allocated memory and zeroed the object. */ +static void +disk_state_reset(void) +{ + config_free_lines(sr_disk_state->Commit); + config_free_lines(sr_disk_state->SharedRandValues); + config_free_lines(sr_disk_state->ExtraLines); + memset(sr_disk_state, 0, sizeof(*sr_disk_state)); + sr_disk_state->magic_ = SR_DISK_STATE_MAGIC; + sr_disk_state->TorVersion = tor_strdup(get_version()); +} + +/* Update our disk state based on our global SR state. */ +static void +disk_state_update(void) +{ + config_line_t **next, *line; + + tor_assert(sr_disk_state); + tor_assert(sr_state); + + /* Reset current disk state. */ + disk_state_reset(); + + /* First, update elements that we don't need to iterate over a list to + * construct something. */ + sr_disk_state->Version = sr_state->version; + sr_disk_state->ValidUntil = sr_state->valid_until; + sr_disk_state->ValidAfter = sr_state->valid_after; + + /* Shared random values. */ + next = &sr_disk_state->SharedRandValues; + *next = NULL; + if (sr_state->previous_srv != NULL) { + *next = line = tor_malloc_zero(sizeof(config_line_t)); + line->key = tor_strdup(dstate_prev_srv_key); + disk_state_put_srv_line(sr_state->previous_srv, line); + next = &(line->next); + } + if (sr_state->current_srv != NULL) { + *next = line = tor_malloc_zero(sizeof(*line)); + line->key = tor_strdup(dstate_cur_srv_key); + disk_state_put_srv_line(sr_state->current_srv, line); + next = &(line->next); + } + + /* Parse the commits and construct config line(s). */ + next = &sr_disk_state->Commit; + DIGESTMAP_FOREACH(sr_state->commits, key, sr_commit_t *, commit) { + *next = line = tor_malloc_zero(sizeof(*line)); + line->key = tor_strdup(dstate_commit_key); + disk_state_put_commit_line(commit, line); + next = &(line->next); + } DIGESTMAP_FOREACH_END; +} + +/* Load state from disk and put it into our disk state. If the state passes + * validation, our global state will be updated with it. Return 0 on + * success. On error, -EINVAL is returned if the state on disk did contained + * something malformed or is unreadable. -ENOENT is returned indicating that + * the state file is either empty of non existing. */ +static int +disk_state_load_from_disk(void) +{ + int ret; + char *fname; + + fname = get_datadir_fname(default_fname); + ret = disk_state_load_from_disk_impl(fname); + tor_free(fname); + + return ret; +} + +/* Helper for disk_state_load_from_disk(). */ +STATIC int +disk_state_load_from_disk_impl(const char *fname) +{ + int ret; + char *content = NULL; + sr_state_t *parsed_state = NULL; + sr_disk_state_t *disk_state = NULL; + + /* Read content of file so we can parse it. */ + if ((content = read_file_to_str(fname, 0, NULL)) == NULL) { + log_warn(LD_FS, "SR: Unable to read SR state file %s", + escaped(fname)); + ret = -errno; + goto error; + } + + { + config_line_t *lines = NULL; + char *errmsg = NULL; + + /* Every error in this code path will return EINVAL. */ + ret = -EINVAL; + if (config_get_lines(content, &lines, 0) < 0) { + config_free_lines(lines); + goto error; + } + + disk_state = disk_state_new(time(NULL)); + config_assign(&state_format, disk_state, lines, 0, 0, &errmsg); + config_free_lines(lines); + if (errmsg) { + log_warn(LD_DIR, "SR: Reading state error: %s", errmsg); + tor_free(errmsg); + goto error; + } + } + + /* So far so good, we've loaded our state file into our disk state. Let's + * validate it and then parse it. */ + if (disk_state_validate(disk_state) < 0) { + ret = -EINVAL; + goto error; + } + + parsed_state = disk_state_parse(disk_state); + if (parsed_state == NULL) { + ret = -EINVAL; + goto error; + } + state_set(parsed_state); + disk_state_set(disk_state); + tor_free(content); + log_notice(LD_DIR, "SR: State loaded successfully from file %s", fname); + return 0; + + error: + disk_state_free(disk_state); + tor_free(content); + return ret; +} + +/* Save the disk state to disk but before that update it from the current + * state so we always have the latest. Return 0 on success else -1. */ +static int +disk_state_save_to_disk(void) +{ + int ret; + char *state, *content = NULL, *fname = NULL; + char tbuf[ISO_TIME_LEN + 1]; + time_t now = time(NULL); + + /* If we didn't have the opportunity to setup an internal disk state, + * don't bother saving something to disk. */ + if (sr_disk_state == NULL) { + ret = 0; + goto done; + } + + /* Make sure that our disk state is up to date with our memory state + * before saving it to disk. */ + disk_state_update(); + state = config_dump(&state_format, NULL, sr_disk_state, 0, 0); + format_local_iso_time(tbuf, now); + tor_asprintf(&content, + "# Tor shared random state file last generated on %s " + "local time\n" + "# Other times below are in UTC\n" + "# Please *do not* edit this file.\n\n%s", + tbuf, state); + tor_free(state); + fname = get_datadir_fname(default_fname); + if (write_str_to_file(fname, content, 0) < 0) { + log_warn(LD_FS, "SR: Unable to write SR state to file %s", fname); + ret = -1; + goto done; + } + ret = 0; + log_debug(LD_DIR, "SR: Saved state to file %s", fname); + + done: + tor_free(fname); + tor_free(content); + return ret; +} + +/* Helper function: return a commit using the RSA fingerprint of the + * authority or NULL if no such commit is known. */ +static sr_commit_t * +state_query_get_commit(const char *rsa_fpr) +{ + tor_assert(rsa_fpr); + return digestmap_get(sr_state->commits, rsa_fpr); +} + +/* Helper function: This handles the GET state action using an + * obj_type and data needed for the action. */ +static void * +state_query_get_(sr_state_object_t obj_type, const void *data) +{ + void *obj = NULL; + + switch (obj_type) { + case SR_STATE_OBJ_COMMIT: + { + obj = state_query_get_commit(data); + break; + } + case SR_STATE_OBJ_COMMITS: + obj = sr_state->commits; + break; + case SR_STATE_OBJ_CURSRV: + obj = sr_state->current_srv; + break; + case SR_STATE_OBJ_PREVSRV: + obj = sr_state->previous_srv; + break; + case SR_STATE_OBJ_PHASE: + obj = &sr_state->phase; + break; + case SR_STATE_OBJ_VALID_AFTER: + default: + tor_assert(0); + } + return obj; +} + +/* Helper function: This handles the PUT state action using an + * obj_type and data needed for the action. */ +static void +state_query_put_(sr_state_object_t obj_type, void *data) +{ + switch (obj_type) { + case SR_STATE_OBJ_COMMIT: + { + sr_commit_t *commit = data; + tor_assert(commit); + commit_add_to_state(commit, sr_state); + break; + } + case SR_STATE_OBJ_CURSRV: + sr_state->current_srv = (sr_srv_t *) data; + break; + case SR_STATE_OBJ_PREVSRV: + sr_state->previous_srv = (sr_srv_t *) data; + break; + case SR_STATE_OBJ_VALID_AFTER: + sr_state->valid_after = *((time_t *) data); + break; + /* It's not allowed to change the phase nor the full commitments map from + * the state. The phase is decided during a strict process post voting and + * the commits should be put individually. */ + case SR_STATE_OBJ_PHASE: + case SR_STATE_OBJ_COMMITS: + default: + tor_assert(0); + } +} + +/* Helper function: This handles the DEL state action using an + * obj_type and data needed for the action. */ +static void +state_query_del_all_(sr_state_object_t obj_type) +{ + switch (obj_type) { + case SR_STATE_OBJ_COMMIT: + { + /* We are in a new protocol run so cleanup commitments. */ + DIGESTMAP_FOREACH_MODIFY(sr_state->commits, key, sr_commit_t *, c) { + sr_commit_free(c); + MAP_DEL_CURRENT(key); + } DIGESTMAP_FOREACH_END; + break; + } + /* The following object are _NOT_ suppose to be removed. */ + case SR_STATE_OBJ_CURSRV: + case SR_STATE_OBJ_PREVSRV: + case SR_STATE_OBJ_PHASE: + case SR_STATE_OBJ_COMMITS: + case SR_STATE_OBJ_VALID_AFTER: + default: + tor_assert(0); + } +} + +/* Query state using an action for an object type obj_type. + * The data pointer needs to point to an object that the action needs + * to use and if anything is required to be returned, it is stored in + * out. + * + * This mechanism exists so we have one single point where we synchronized + * our memory state with our disk state for every actions that changes it. + * We then trigger a write on disk immediately. + * + * This should be the only entry point to our memory state. It's used by all + * our state accessors and should be in the future. */ +static void +state_query(sr_state_action_t action, sr_state_object_t obj_type, + void *data, void **out) +{ + switch (action) { + case SR_STATE_ACTION_GET: + *out = state_query_get_(obj_type, data); + break; + case SR_STATE_ACTION_PUT: + state_query_put_(obj_type, data); + break; + case SR_STATE_ACTION_DEL_ALL: + state_query_del_all_(obj_type); + break; + case SR_STATE_ACTION_SAVE: + /* Only trigger a disk state save. */ + break; + default: + tor_assert(0); + } + + /* If the action actually changes the state, immediately save it to disk. + * The following will sync the state -> disk state and then save it. */ + if (action != SR_STATE_ACTION_GET) { + disk_state_save_to_disk(); + } +} + +/* Set valid after time in the our state. */ +void +sr_state_set_valid_after(time_t valid_after) +{ + state_query(SR_STATE_ACTION_PUT, SR_STATE_OBJ_VALID_AFTER, + (void *) &valid_after, NULL); +} + +/* Return the phase we are currently in according to our state. */ +sr_phase_t +sr_state_get_phase(void) +{ + void *ptr; + state_query(SR_STATE_ACTION_GET, SR_STATE_OBJ_PHASE, NULL, &ptr); + return *(sr_phase_t *) ptr; +} + +/* Return the previous SRV value from our state. Value CAN be NULL. */ +sr_srv_t * +sr_state_get_previous_srv(void) +{ + sr_srv_t *srv; + state_query(SR_STATE_ACTION_GET, SR_STATE_OBJ_PREVSRV, NULL, + (void *) &srv); + return srv; +} + +/* Set the current SRV value from our state. Value CAN be NULL. The srv + * object ownership is transfered to the state object. */ +void +sr_state_set_previous_srv(const sr_srv_t *srv) +{ + state_query(SR_STATE_ACTION_PUT, SR_STATE_OBJ_PREVSRV, (void *) srv, + NULL); +} + +/* Return the current SRV value from our state. Value CAN be NULL. */ +sr_srv_t * +sr_state_get_current_srv(void) +{ + sr_srv_t *srv; + state_query(SR_STATE_ACTION_GET, SR_STATE_OBJ_CURSRV, NULL, + (void *) &srv); + return srv; +} + +/* Set the current SRV value from our state. Value CAN be NULL. The srv + * object ownership is transfered to the state object. */ +void +sr_state_set_current_srv(const sr_srv_t *srv) +{ + state_query(SR_STATE_ACTION_PUT, SR_STATE_OBJ_CURSRV, (void *) srv, + NULL); +} + +/* Return a pointer to the commits map from our state. CANNOT be NULL. */ +digestmap_t * +sr_state_get_commits(void) +{ + digestmap_t *commits; + state_query(SR_STATE_ACTION_GET, SR_STATE_OBJ_COMMITS, + NULL, (void *) &commits); + tor_assert(commits); + return commits; +} + +/* Return commit object from the given authority digest identity. + * Return NULL if not found. */ +sr_commit_t * +sr_state_get_commit(const char *rsa_fpr) +{ + sr_commit_t *commit; + + tor_assert(rsa_fpr); + + state_query(SR_STATE_ACTION_GET, SR_STATE_OBJ_COMMIT, + (void *) rsa_fpr, (void *) &commit); + return commit; +} + +/* Add commit to the permanent state. The commit object ownership is + * transfered to the state so the caller MUST not free it. */ +void +sr_state_add_commit(sr_commit_t *commit) +{ + tor_assert(commit); + + /* Put the commit to the global state. */ + state_query(SR_STATE_ACTION_PUT, SR_STATE_OBJ_COMMIT, + (void *) commit, NULL); + + log_debug(LD_DIR, "SR: Commit from %s has been added to our state.", + commit->rsa_identity_fpr); +} + +/* Remove all commits from our state. */ +void +sr_state_delete_commits(void) +{ + state_query(SR_STATE_ACTION_DEL_ALL, SR_STATE_OBJ_COMMIT, NULL, NULL); +} + +/* Set the fresh SRV flag from our state. This doesn't need to trigger a + * disk state synchronization so we directly change the state. */ +void +sr_state_set_fresh_srv(void) +{ + sr_state->is_srv_fresh = 1; +} + +/* Unset the fresh SRV flag from our state. This doesn't need to trigger a + * disk state synchronization so we directly change the state. */ +void +sr_state_unset_fresh_srv(void) +{ + sr_state->is_srv_fresh = 0; +} + +/* Return the value of the fresh SRV flag. */ +unsigned int +sr_state_srv_is_fresh(void) +{ + return sr_state->is_srv_fresh; +} + +/* Cleanup and free our disk and memory state. */ +void +sr_state_free(void) +{ + state_free(sr_state); + disk_state_free(sr_disk_state); + /* Nullify our global state. */ + sr_state = NULL; + sr_disk_state = NULL; +} + +/* Save our current state in memory to disk. */ +void +sr_state_save(void) +{ + /* Query a SAVE action on our current state so it's synced and saved. */ + state_query(SR_STATE_ACTION_SAVE, 0, NULL, NULL); +} + +/* Initialize the disk and memory state. + * + * If save_to_disk is set to 1, the state is immediately saved to disk after + * creation else it's not thus only kept in memory. + * If read_from_disk is set to 1, we try to load the state from the disk and + * if not found, a new state is created. + * + * Return 0 on success else a negative value on error. */ +int +sr_state_init(int save_to_disk, int read_from_disk) +{ + int ret = -ENOENT; + time_t now = time(NULL); + + /* We shouldn't have those assigned. */ + tor_assert(sr_disk_state == NULL); + tor_assert(sr_state == NULL); + + /* First, try to load the state from disk. */ + if (read_from_disk) { + ret = disk_state_load_from_disk(); + } + + if (ret < 0) { + switch (-ret) { + case EINVAL: + /* We have a state on disk but it contains something we couldn't parse + * or an invalid entry in the state file. Let's remove it since it's + * obviously unusable and replace it by an new fresh state below. */ + case ENOENT: + { + /* No state on disk so allocate our states for the first time. */ + sr_state_t *new_state = state_new(default_fname, now); + sr_disk_state_t *new_disk_state = disk_state_new(now); + state_set(new_state); + /* It's important to set our disk state pointer since the save call + * below uses it to synchronized it with our memory state. */ + disk_state_set(new_disk_state); + /* No entry, let's save our new state to disk. */ + if (save_to_disk && disk_state_save_to_disk() < 0) { + goto error; + } + break; + } + default: + /* Big problem. Not possible. */ + tor_assert(0); + } + } + return 0; + + error: + return -1; +} diff --git a/src/or/shared_random_state.h b/src/or/shared_random_state.h new file mode 100644 index 0000000000..a9a80eed4c --- /dev/null +++ b/src/or/shared_random_state.h @@ -0,0 +1,126 @@ +/* Copyright (c) 2016, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_SHARED_RANDOM_STATE_H +#define TOR_SHARED_RANDOM_STATE_H + +#include "shared_random.h" + +/* Action that can be performed on the state for any objects. */ +typedef enum { + SR_STATE_ACTION_GET = 1, + SR_STATE_ACTION_PUT = 2, + SR_STATE_ACTION_DEL_ALL = 3, + SR_STATE_ACTION_SAVE = 4, +} sr_state_action_t; + +/* Object in the state that can be queried through the state API. */ +typedef enum { + /* Will return a single commit using an authority identity key. */ + SR_STATE_OBJ_COMMIT, + /* Returns the entire list of commits from the state. */ + SR_STATE_OBJ_COMMITS, + /* Return the current SRV object pointer. */ + SR_STATE_OBJ_CURSRV, + /* Return the previous SRV object pointer. */ + SR_STATE_OBJ_PREVSRV, + /* Return the phase. */ + SR_STATE_OBJ_PHASE, + /* Get or Put the valid after time. */ + SR_STATE_OBJ_VALID_AFTER, +} sr_state_object_t; + +/* State of the protocol. It's also saved on disk in fname. This data + * structure MUST be synchronized at all time with the one on disk. */ +typedef struct sr_state_t { + /* Filename of the state file on disk. */ + char *fname; + /* Version of the protocol. */ + uint8_t version; + /* The valid-after of the voting period we have prepared the state for. */ + time_t valid_after; + /* Until when is this state valid? */ + time_t valid_until; + /* Protocol phase. */ + sr_phase_t phase; + + /* Number of runs completed. */ + uint64_t n_protocol_runs; + /* The number of commitment rounds we've performed in this protocol run. */ + unsigned int n_commit_rounds; + /* The number of reveal rounds we've performed in this protocol run. */ + unsigned int n_reveal_rounds; + + /* A map of all the received commitments for this protocol run. This is + * indexed by authority RSA identity digest. */ + digestmap_t *commits; + + /* Current and previous shared random value. */ + sr_srv_t *previous_srv; + sr_srv_t *current_srv; + + /* Indicate if the state contains an SRV that was _just_ generated. This is + * used during voting so that we know whether to use the super majority rule + * or not when deciding on keeping it for the consensus. It is _always_ set + * to 0 post consensus. + * + * EDGE CASE: if an authority computes a new SRV then immediately reboots + * and, once back up, votes for the current round, it won't know if the + * SRV is fresh or not ultimately making it _NOT_ use the super majority + * when deciding to put or not the SRV in the consensus. This is for now + * an acceptable very rare edge case. */ + unsigned int is_srv_fresh:1; +} sr_state_t; + +/* Persistent state of the protocol, as saved to disk. */ +typedef struct sr_disk_state_t { + uint32_t magic_; + /* Version of the protocol. */ + int Version; + /* Version of our running tor. */ + char *TorVersion; + /* Creation time of this state */ + time_t ValidAfter; + /* State valid until? */ + time_t ValidUntil; + /* All commits seen that are valid. */ + config_line_t *Commit; + /* Previous and current shared random value. */ + config_line_t *SharedRandValues; + /* Extra Lines for configuration we might not know. */ + config_line_t *ExtraLines; +} sr_disk_state_t; + +/* API */ + +/* Public methods: */ + +/* Private methods (only used by shared-random.c): */ + +void sr_state_set_valid_after(time_t valid_after); +sr_phase_t sr_state_get_phase(void); +sr_srv_t *sr_state_get_previous_srv(void); +sr_srv_t *sr_state_get_current_srv(void); +void sr_state_set_previous_srv(const sr_srv_t *srv); +void sr_state_set_current_srv(const sr_srv_t *srv); +void sr_state_clean_srvs(void); +digestmap_t *sr_state_get_commits(void); +sr_commit_t *sr_state_get_commit(const char *rsa_fpr); +void sr_state_add_commit(sr_commit_t *commit); +void sr_state_delete_commits(void); +unsigned int sr_state_srv_is_fresh(void); +void sr_state_set_fresh_srv(void); +void sr_state_unset_fresh_srv(void); +int sr_state_init(int save_to_disk, int read_from_disk); +void sr_state_save(void); +void sr_state_free(void); + +#ifdef SHARED_RANDOM_STATE_PRIVATE + +STATIC int disk_state_load_from_disk_impl(const char *fname); +STATIC sr_phase_t get_sr_protocol_phase(time_t valid_after); +STATIC time_t get_state_valid_until_time(time_t now); + +#endif /* SHARED_RANDOM_STATE_PRIVATE */ + +#endif /* TOR_SHARED_RANDOM_STATE_H */ From 5b183328fdd4bf81a3a38afe88d2766e61773fb9 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 3 May 2016 10:57:49 -0400 Subject: [PATCH 03/26] prop250: Add commit and SR values generation code This adds the logic of commit and SR values generation. Furthermore, the concept of a protocol run is added that is commit is generated at the right time as well as SR values which are also rotated before a new protocol run. Signed-off-by: George Kadianakis Signed-off-by: David Goulet --- src/or/shared_random.c | 315 ++++++++++++++++++++++++++++++++++- src/or/shared_random.h | 14 ++ src/or/shared_random_state.c | 186 ++++++++++++++++++++- src/or/shared_random_state.h | 11 ++ 4 files changed, 516 insertions(+), 10 deletions(-) diff --git a/src/or/shared_random.c b/src/or/shared_random.c index 447ab27f44..dd567bc06a 100644 --- a/src/or/shared_random.c +++ b/src/or/shared_random.c @@ -37,6 +37,35 @@ commit_new(const char *rsa_identity_fpr) return commit; } +/* Issue a log message describing commit. */ +static void +commit_log(const sr_commit_t *commit) +{ + tor_assert(commit); + + log_debug(LD_DIR, "SR: Commit from %s", commit->rsa_identity_fpr); + + if (commit->commit_ts >= 0) { + log_debug(LD_DIR, "SR: Commit: [TS: %ld] [Encoded: %s]", + commit->commit_ts, commit->encoded_commit); + } + + if (commit->reveal_ts >= 0) { + log_debug(LD_DIR, "SR: Reveal: [TS: %ld] [Encoded: %s]", + commit->reveal_ts, safe_str(commit->encoded_reveal)); + } else { + log_debug(LD_DIR, "SR: Reveal: UNKNOWN"); + } +} + +/* Return true iff the commit contains an encoded reveal value. */ +STATIC int +commit_has_reveal_value(const sr_commit_t *commit) +{ + return !tor_mem_is_zero(commit->encoded_reveal, + sizeof(commit->encoded_reveal)); +} + /* Parse the encoded commit. The format is: * base64-encode( TIMESTAMP || H(REVEAL) ) * @@ -144,6 +173,62 @@ reveal_decode(const char *encoded, sr_commit_t *commit) return -1; } + +/* Encode a reveal element using a given commit object to dst which is a + * buffer large enough to put the base64-encoded reveal construction. The + * format is as follow: + * REVEAL = base64-encode( TIMESTAMP || H(RN) ) + * Return base64 encoded length on success else a negative value. + */ +STATIC int +reveal_encode(const sr_commit_t *commit, char *dst, size_t len) +{ + int ret; + size_t offset = 0; + char buf[SR_REVEAL_LEN] = {0}; + + tor_assert(commit); + tor_assert(dst); + + set_uint64(buf, tor_htonll(commit->reveal_ts)); + offset += sizeof(uint64_t); + memcpy(buf + offset, commit->random_number, + sizeof(commit->random_number)); + + /* Let's clean the buffer and then b64 encode it. */ + memset(dst, 0, len); + ret = base64_encode(dst, len, buf, sizeof(buf), 0); + /* Wipe this buffer because it contains our random value. */ + memwipe(buf, 0, sizeof(buf)); + return ret; +} + +/* Encode the given commit object to dst which is a buffer large enough to + * put the base64-encoded commit. The format is as follow: + * COMMIT = base64-encode( TIMESTAMP || H(H(RN)) ) + * Return base64 encoded length on success else a negative value. + */ +STATIC int +commit_encode(const sr_commit_t *commit, char *dst, size_t len) +{ + size_t offset = 0; + char buf[SR_COMMIT_LEN] = {0}; + + tor_assert(commit); + tor_assert(dst); + + /* First is the timestamp (8 bytes). */ + set_uint64(buf, tor_htonll((uint64_t) commit->commit_ts)); + offset += sizeof(uint64_t); + /* and then the hashed reveal. */ + memcpy(buf + offset, commit->hashed_reveal, + sizeof(commit->hashed_reveal)); + + /* Clean the buffer and then b64 encode it. */ + memset(dst, 0, len); + return base64_encode(dst, len, buf, sizeof(buf), 0); +} + /* Cleanup both our global state and disk state. */ static void sr_cleanup(void) @@ -151,6 +236,96 @@ sr_cleanup(void) sr_state_free(); } +/* Using commit, return a newly allocated string containing the commit + * information that should be used during SRV calculation. It's the caller + * responsibility to free the memory. Return NULL if this is not a commit to be + * used for SRV calculation. */ +static char * +get_srv_element_from_commit(const sr_commit_t *commit) +{ + char *element; + tor_assert(commit); + + if (!commit_has_reveal_value(commit)) { + return NULL; + } + + tor_asprintf(&element, "%s%s", commit->rsa_identity_fpr, + commit->encoded_reveal); + return element; +} + +/* Return a srv object that is built with the construction: + * SRV = SHA3-256("shared-random" | INT_8(reveal_num) | + * INT_8(version) | HASHED_REVEALS | previous_SRV) + * This function cannot fail. */ +static sr_srv_t * +generate_srv(const char *hashed_reveals, uint8_t reveal_num, + const sr_srv_t *previous_srv) +{ + char msg[DIGEST256_LEN + SR_SRV_MSG_LEN] = {0}; + size_t offset = 0; + sr_srv_t *srv; + + tor_assert(hashed_reveals); + + /* Add the invariant token. */ + memcpy(msg, SR_SRV_TOKEN, SR_SRV_TOKEN_LEN); + offset += SR_SRV_TOKEN_LEN; + set_uint8(msg + offset, reveal_num); + offset += 1; + set_uint8(msg + offset, SR_PROTO_VERSION); + offset += 1; + memcpy(msg + offset, hashed_reveals, DIGEST256_LEN); + offset += DIGEST256_LEN; + if (previous_srv != NULL) { + memcpy(msg + offset, previous_srv->value, sizeof(previous_srv->value)); + } + + /* Ok we have our message and key for the HMAC computation, allocate our + * srv object and do the last step. */ + srv = tor_malloc_zero(sizeof(*srv)); + crypto_digest256((char *) srv->value, msg, sizeof(msg), SR_DIGEST_ALG); + srv->num_reveals = reveal_num; + + { + /* Debugging. */ + char srv_hash_encoded[SR_SRV_VALUE_BASE64_LEN + 1]; + sr_srv_encode(srv_hash_encoded, srv); + log_debug(LD_DIR, "SR: Generated SRV: %s", srv_hash_encoded); + } + return srv; +} + +/* Compare reveal values and return the result. This should exclusively be + * used by smartlist_sort(). */ +static int +compare_reveal_(const void **_a, const void **_b) +{ + const sr_commit_t *a = *_a, *b = *_b; + return fast_memcmp(a->hashed_reveal, b->hashed_reveal, + sizeof(a->hashed_reveal)); +} + +/* Encode the given shared random value and put it in dst. Destination + * buffer must be at least SR_SRV_VALUE_BASE64_LEN plus the NULL byte. */ +void +sr_srv_encode(char *dst, const sr_srv_t *srv) +{ + int ret; + /* Extra byte for the NULL terminated char. */ + char buf[SR_SRV_VALUE_BASE64_LEN + 1]; + + tor_assert(dst); + tor_assert(srv); + + ret = base64_encode(buf, sizeof(buf), (const char *) srv->value, + sizeof(srv->value), 0); + /* Always expect the full length without the NULL byte. */ + tor_assert(ret == (sizeof(buf) - 1)); + strlcpy(dst, buf, sizeof(buf)); +} + /* Free a commit object. */ void sr_commit_free(sr_commit_t *commit) @@ -163,6 +338,123 @@ sr_commit_free(sr_commit_t *commit) tor_free(commit); } +/* Generate the commitment/reveal value for the protocol run starting at + * timestamp. my_rsa_cert is our authority RSA certificate. */ +sr_commit_t * +sr_generate_our_commit(time_t timestamp, const authority_cert_t *my_rsa_cert) +{ + sr_commit_t *commit = NULL; + char fingerprint[FINGERPRINT_LEN+1]; + + tor_assert(my_rsa_cert); + + /* Get our RSA identity fingerprint */ + if (crypto_pk_get_fingerprint(my_rsa_cert->identity_key, + fingerprint, 0) < 0) { + goto error; + } + + /* New commit with our identity key. */ + commit = commit_new(fingerprint); + + /* Generate the reveal random value */ + crypto_strongest_rand(commit->random_number, + sizeof(commit->random_number)); + commit->commit_ts = commit->reveal_ts = timestamp; + + /* Now get the base64 blob that corresponds to our reveal */ + if (reveal_encode(commit, commit->encoded_reveal, + sizeof(commit->encoded_reveal)) < 0) { + log_err(LD_DIR, "SR: Unable to encode our reveal value!"); + goto error; + } + + /* Now let's create the commitment */ + tor_assert(commit->alg == SR_DIGEST_ALG); + /* The invariant length is used here since the encoded reveal variable + * has an extra byte added for the NULL terminated byte. */ + if (crypto_digest256(commit->hashed_reveal, commit->encoded_reveal, + SR_REVEAL_BASE64_LEN, commit->alg)) { + goto error; + } + + /* Now get the base64 blob that corresponds to our commit. */ + if (commit_encode(commit, commit->encoded_commit, + sizeof(commit->encoded_commit)) < 0) { + log_err(LD_DIR, "SR: Unable to encode our commit value!"); + goto error; + } + + log_debug(LD_DIR, "SR: Generated our commitment:"); + commit_log(commit); + return commit; + + error: + sr_commit_free(commit); + return NULL; +} + +/* Compute the shared random value based on the active commits in our state. */ +void +sr_compute_srv(void) +{ + size_t reveal_num = 0; + char *reveals = NULL; + smartlist_t *chunks, *commits; + digestmap_t *state_commits; + + /* Computing a shared random value in the commit phase is very wrong. This + * should only happen at the very end of the reveal phase when a new + * protocol run is about to start. */ + tor_assert(sr_state_get_phase() == SR_PHASE_REVEAL); + state_commits = sr_state_get_commits(); + + commits = smartlist_new(); + chunks = smartlist_new(); + + /* We must make a list of commit ordered by authority fingerprint in + * ascending order as specified by proposal 250. */ + DIGESTMAP_FOREACH(state_commits, key, sr_commit_t *, c) { + smartlist_add(commits, c); + } DIGESTMAP_FOREACH_END; + smartlist_sort(commits, compare_reveal_); + + /* Now for each commit for that sorted list in ascending order, we'll + * build the element for each authority that needs to go into the srv + * computation. */ + SMARTLIST_FOREACH_BEGIN(commits, const sr_commit_t *, c) { + char *element = get_srv_element_from_commit(c); + if (element) { + smartlist_add(chunks, element); + reveal_num++; + } + } SMARTLIST_FOREACH_END(c); + smartlist_free(commits); + + { + /* Join all reveal values into one giant string that we'll hash so we + * can generated our shared random value. */ + sr_srv_t *current_srv; + char hashed_reveals[DIGEST256_LEN]; + reveals = smartlist_join_strings(chunks, "", 0, NULL); + SMARTLIST_FOREACH(chunks, char *, s, tor_free(s)); + smartlist_free(chunks); + if (crypto_digest256(hashed_reveals, reveals, strlen(reveals), + SR_DIGEST_ALG)) { + goto end; + } + tor_assert(reveal_num < UINT8_MAX); + current_srv = generate_srv(hashed_reveals, (uint8_t) reveal_num, + sr_state_get_previous_srv()); + sr_state_set_current_srv(current_srv); + /* We have a fresh SRV, flag our state. */ + sr_state_set_fresh_srv(); + } + + end: + tor_free(reveals); +} + /* Parse a list of arguments from a SRV value either from a vote, consensus * or from our disk state and return a newly allocated srv object. NULL is * returned on error. @@ -174,7 +466,7 @@ sr_srv_t * sr_parse_srv(const smartlist_t *args) { char *value; - int num_reveals, ok; + int num_reveals, ok, ret; sr_srv_t *srv = NULL; tor_assert(args); @@ -189,13 +481,24 @@ sr_parse_srv(const smartlist_t *args) if (!ok) { goto end; } - srv = tor_malloc_zero(sizeof(*srv)); - srv->num_reveals = num_reveals; - /* Second and last argument is the shared random value it self. */ value = smartlist_get(args, 1); - base16_decode((char *) srv->value, sizeof(srv->value), value, - HEX_DIGEST256_LEN); + if (strlen(value) != SR_SRV_VALUE_BASE64_LEN) { + goto end; + } + + srv = tor_malloc_zero(sizeof(*srv)); + srv->num_reveals = num_reveals; + /* We substract one byte from the srclen because the function ignores the + * '=' character in the given buffer. This is broken but it's a documented + * behavior of the implementation. */ + ret = base64_decode((char *) srv->value, sizeof(srv->value), value, + SR_SRV_VALUE_BASE64_LEN - 1); + if (ret != sizeof(srv->value)) { + tor_free(srv); + srv = NULL; + goto end; + } end: return srv; } diff --git a/src/or/shared_random.h b/src/or/shared_random.h index 447de0c4b5..878bfbff45 100644 --- a/src/or/shared_random.h +++ b/src/or/shared_random.h @@ -42,6 +42,11 @@ * Formula is taken from base64_encode_size. This adds up to 56 bytes. */ #define SR_REVEAL_BASE64_LEN \ (((SR_REVEAL_LEN - 1) / 3) * 4 + 4) +/* Length of base64 encoded shared random value. It's 32 bytes long so 44 + * bytes from the base64_encode_size formula. That includes the '=' + * character at the end. */ +#define SR_SRV_VALUE_BASE64_LEN \ + (((DIGEST256_LEN - 1) / 3) * 4 + 4) /* Protocol phase. */ typedef enum { @@ -97,18 +102,27 @@ typedef struct sr_commit_t { int sr_init(int save_to_disk); void sr_save_and_cleanup(void); void sr_commit_free(sr_commit_t *commit); +void sr_srv_encode(char *dst, const sr_srv_t *srv); /* Private methods (only used by shared_random_state.c): */ sr_commit_t *sr_parse_commit(const smartlist_t *args); sr_srv_t *sr_parse_srv(const smartlist_t *args); +void sr_compute_srv(void); +sr_commit_t *sr_generate_our_commit(time_t timestamp, + const authority_cert_t *my_rsa_cert); #ifdef SHARED_RANDOM_PRIVATE +/* Encode */ +STATIC int reveal_encode(const sr_commit_t *commit, char *dst, size_t len); +STATIC int commit_encode(const sr_commit_t *commit, char *dst, size_t len); /* Decode. */ STATIC int commit_decode(const char *encoded, sr_commit_t *commit); STATIC int reveal_decode(const char *encoded, sr_commit_t *commit); +STATIC int commit_has_reveal_value(const sr_commit_t *commit); + #endif /* SHARED_RANDOM_PRIVATE */ #endif /* TOR_SHARED_RANDOM_H */ diff --git a/src/or/shared_random_state.c b/src/or/shared_random_state.c index 9fd3b27546..87bdfc048f 100644 --- a/src/or/shared_random_state.c +++ b/src/or/shared_random_state.c @@ -22,6 +22,9 @@ /* Default filename of the shared random state on disk. */ static const char default_fname[] = "sr-state"; +/* String representation of a protocol phase. */ +static const char *phase_str[] = { "unknown", "commit", "reveal" }; + /* Our shared random protocol state. There is only one possible state per * protocol run so this is the global state which is reset at every run once * the shared random value has been computed. */ @@ -88,6 +91,25 @@ static const config_format_t state_format = { &state_extra_var, }; +/* Return a string representation of a protocol phase. */ +STATIC const char * +get_phase_str(sr_phase_t phase) +{ + const char *the_string = NULL; + + switch (phase) { + case SR_PHASE_COMMIT: + case SR_PHASE_REVEAL: + the_string = phase_str[phase]; + break; + default: + /* Unknown phase shouldn't be possible. */ + tor_assert(0); + } + + return the_string; +} + /* Return the voting interval of the tor vote subsystem. */ static int get_voting_interval(void) @@ -554,7 +576,7 @@ disk_state_put_commit_line(const sr_commit_t *commit, config_line_t *line) static void disk_state_put_srv_line(const sr_srv_t *srv, config_line_t *line) { - char encoded[HEX_DIGEST256_LEN + 1]; + char encoded[SR_SRV_VALUE_BASE64_LEN + 1]; tor_assert(line); @@ -563,8 +585,7 @@ disk_state_put_srv_line(const sr_srv_t *srv, config_line_t *line) if (srv == NULL) { return; } - base16_encode(encoded, sizeof(encoded), (const char *) srv->value, - sizeof(srv->value)); + sr_srv_encode(encoded, srv); tor_asprintf(&line->value, "%d %s", srv->num_reveals, encoded); } @@ -748,6 +769,82 @@ disk_state_save_to_disk(void) return ret; } +/* Reset our state to prepare for a new protocol run. Once this returns, all + * commits in the state will be removed and freed. */ +STATIC void +reset_state_for_new_protocol_run(time_t valid_after) +{ + tor_assert(sr_state); + + /* Keep counters in track */ + sr_state->n_reveal_rounds = 0; + sr_state->n_commit_rounds = 0; + sr_state->n_protocol_runs++; + + /* Reset valid-until */ + sr_state->valid_until = get_state_valid_until_time(valid_after); + sr_state->valid_after = valid_after; + + /* We are in a new protocol run so cleanup commits. */ + sr_state_delete_commits(); +} + +/* Rotate SRV value by freeing the previous value, assigning the current + * value to the previous one and nullifying the current one. */ +STATIC void +state_rotate_srv(void) +{ + /* Get a pointer to the previous SRV so we can free it after rotation. */ + sr_srv_t *previous_srv = sr_state_get_previous_srv(); + /* Set previous SRV with the current one. */ + sr_state_set_previous_srv(sr_state_get_current_srv()); + /* Nullify the current srv. */ + sr_state_set_current_srv(NULL); + tor_free(previous_srv); +} + +/* This is the first round of the new protocol run starting at + * valid_after. Do the necessary housekeeping. */ +STATIC void +new_protocol_run(time_t valid_after) +{ + sr_commit_t *our_commitment = NULL; + + /* Only compute the srv at the end of the reveal phase. */ + if (sr_state->phase == SR_PHASE_REVEAL) { + /* We are about to compute a new shared random value that will be set in + * our state as the current value so rotate values. */ + state_rotate_srv(); + /* Compute the shared randomness value of the day. */ + sr_compute_srv(); + } + + /* Prepare for the new protocol run by reseting the state */ + reset_state_for_new_protocol_run(valid_after); + + /* Do some logging */ + log_info(LD_DIR, "SR: Protocol run #%" PRIu64 " starting!", + sr_state->n_protocol_runs); + + /* Generate fresh commitments for this protocol run */ + our_commitment = sr_generate_our_commit(valid_after, + get_my_v3_authority_cert()); + if (our_commitment) { + /* Add our commitment to our state. In case we are unable to create one + * (highly unlikely), we won't vote for this protocol run since our + * commitment won't be in our state. */ + sr_state_add_commit(our_commitment); + } +} + +/* Return 1 iff the next_phase is a phase transition from the current + * phase that is it's different. */ +STATIC int +is_phase_transition(sr_phase_t next_phase) +{ + return sr_state->phase != next_phase; +} + /* Helper function: return a commit using the RSA fingerprint of the * authority or NULL if no such commit is known. */ static sr_commit_t * @@ -756,7 +853,6 @@ state_query_get_commit(const char *rsa_fpr) tor_assert(rsa_fpr); return digestmap_get(sr_state->commits, rsa_fpr); } - /* Helper function: This handles the GET state action using an * obj_type and data needed for the action. */ static void * @@ -941,6 +1037,20 @@ sr_state_set_current_srv(const sr_srv_t *srv) NULL); } +/* Clean all the SRVs in our state. */ +void +sr_state_clean_srvs(void) +{ + sr_srv_t *previous_srv = sr_state_get_previous_srv(); + sr_srv_t *current_srv = sr_state_get_current_srv(); + + tor_free(previous_srv); + sr_state_set_previous_srv(NULL); + + tor_free(current_srv); + sr_state_set_current_srv(NULL); +} + /* Return a pointer to the commits map from our state. CANNOT be NULL. */ digestmap_t * sr_state_get_commits(void) @@ -952,6 +1062,68 @@ sr_state_get_commits(void) return commits; } +/* Update the current SR state as needed for the upcoming voting round at + * valid_after. */ +void +sr_state_update(time_t valid_after) +{ + sr_phase_t next_phase; + + tor_assert(sr_state); + + /* Don't call this function twice in the same voting period. */ + if (valid_after <= sr_state->valid_after) { + log_info(LD_DIR, "SR: Asked to update state twice. Ignoring."); + return; + } + + /* Get phase of upcoming round. */ + next_phase = get_sr_protocol_phase(valid_after); + + /* If we are transitioning to a new protocol phase, prepare the stage. */ + if (is_phase_transition(next_phase)) { + if (next_phase == SR_PHASE_COMMIT) { + /* Going into commit phase means we are starting a new protocol run. */ + new_protocol_run(valid_after); + } + /* Set the new phase for this round */ + sr_state->phase = next_phase; + } else if (sr_state->phase == SR_PHASE_COMMIT && + digestmap_size(sr_state->commits) == 0) { + /* We are _NOT_ in a transition phase so if we are in the commit phase + * and have no commit, generate one. Chances are that we are booting up + * so let's have a commit in our state for the next voting period. */ + sr_commit_t *our_commit = + sr_generate_our_commit(valid_after, get_my_v3_authority_cert()); + if (our_commit) { + /* Add our commitment to our state. In case we are unable to create one + * (highly unlikely), we won't vote for this protocol run since our + * commitment won't be in our state. */ + sr_state_add_commit(our_commit); + } + } + + sr_state_set_valid_after(valid_after); + + /* Count the current round */ + if (sr_state->phase == SR_PHASE_COMMIT) { + /* invariant check: we've not entered reveal phase yet */ + tor_assert(sr_state->n_reveal_rounds == 0); + sr_state->n_commit_rounds++; + } else { + sr_state->n_reveal_rounds++; + } + + { /* Debugging. */ + char tbuf[ISO_TIME_LEN + 1]; + format_iso_time(tbuf, valid_after); + log_info(LD_DIR, "SR: State prepared for new voting period (%s). " + "Current phase is %s (%d/%d).", + tbuf, get_phase_str(sr_state->phase), + sr_state->n_commit_rounds, sr_state->n_reveal_rounds); + } +} + /* Return commit object from the given authority digest identity. * Return NULL if not found. */ sr_commit_t * @@ -1079,6 +1251,12 @@ sr_state_init(int save_to_disk, int read_from_disk) tor_assert(0); } } + /* We have a state in memory, let's make sure it's updated for the current + * and next voting round. */ + { + time_t valid_after = get_next_valid_after_time(now); + sr_state_update(valid_after); + } return 0; error: diff --git a/src/or/shared_random_state.h b/src/or/shared_random_state.h index a9a80eed4c..d9d97510da 100644 --- a/src/or/shared_random_state.h +++ b/src/or/shared_random_state.h @@ -95,6 +95,8 @@ typedef struct sr_disk_state_t { /* Public methods: */ +void sr_state_update(time_t valid_after); + /* Private methods (only used by shared-random.c): */ void sr_state_set_valid_after(time_t valid_after); @@ -108,6 +110,8 @@ digestmap_t *sr_state_get_commits(void); sr_commit_t *sr_state_get_commit(const char *rsa_fpr); void sr_state_add_commit(sr_commit_t *commit); void sr_state_delete_commits(void); +void sr_state_copy_reveal_info(sr_commit_t *saved_commit, + const sr_commit_t *commit); unsigned int sr_state_srv_is_fresh(void); void sr_state_set_fresh_srv(void); void sr_state_unset_fresh_srv(void); @@ -118,8 +122,15 @@ void sr_state_free(void); #ifdef SHARED_RANDOM_STATE_PRIVATE STATIC int disk_state_load_from_disk_impl(const char *fname); + STATIC sr_phase_t get_sr_protocol_phase(time_t valid_after); + STATIC time_t get_state_valid_until_time(time_t now); +STATIC const char *get_phase_str(sr_phase_t phase); +STATIC void reset_state_for_new_protocol_run(time_t valid_after); +STATIC void new_protocol_run(time_t valid_after); +STATIC void state_rotate_srv(void); +STATIC int is_phase_transition(sr_phase_t next_phase); #endif /* SHARED_RANDOM_STATE_PRIVATE */ From ca6ceec112f05ce68097429089ee428010c8b8d0 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 3 May 2016 11:21:17 -0400 Subject: [PATCH 04/26] prop250: Put commits and SRVs in votes/consensus This commit adds the commit(s) line in the vote as well as the SR values. It also has the mechanism to add the majority SRVs in the consensus. Signed-off-by: George Kadianakis Signed-off-by: David Goulet --- doc/tor.1.txt | 6 + src/or/config.c | 1 + src/or/dirvote.c | 22 ++- src/or/dirvote.h | 7 +- src/or/networkstatus.c | 10 ++ src/or/or.h | 21 +++ src/or/shared_random.c | 301 +++++++++++++++++++++++++++++++++++++++++ src/or/shared_random.h | 9 +- 8 files changed, 372 insertions(+), 5 deletions(-) diff --git a/doc/tor.1.txt b/doc/tor.1.txt index 65d3c2d5f5..8508aaaef8 100644 --- a/doc/tor.1.txt +++ b/doc/tor.1.txt @@ -2225,6 +2225,12 @@ on the public Tor network. in a journal if it is new, or if it differs from the most recently accepted pinning for one of the keys it contains. (Default: 0) +[[AuthDirSharedRandomness]] **AuthDirSharedRandomness** **0**|**1**:: + Authoritative directories only. Switch for the shared random protocol. + If zero, the authority won't participate in the protocol. If non-zero + (default), the flag "shared-rand-participate" is added to the authority + vote indicating participation in the protocol. (Default: 1) + [[BridgePassword]] **BridgePassword** __Password__:: If set, contains an HTTP authenticator that tells a bridge authority to serve all requested bridge information. Used by the (only partially diff --git a/src/or/config.c b/src/or/config.c index cdd4f10e92..e1a447357a 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -439,6 +439,7 @@ static config_var_t option_vars_[] = { V(UseNTorHandshake, AUTOBOOL, "1"), V(User, STRING, NULL), V(UserspaceIOCPBuffers, BOOL, "0"), + V(AuthDirSharedRandomness, BOOL, "1"), OBSOLETE("V1AuthoritativeDirectory"), OBSOLETE("V2AuthoritativeDirectory"), VAR("V3AuthoritativeDirectory",BOOL, V3AuthoritativeDir, "0"), diff --git a/src/or/dirvote.c b/src/or/dirvote.c index a08f6eceaa..af093deaf4 100644 --- a/src/or/dirvote.c +++ b/src/or/dirvote.c @@ -15,10 +15,12 @@ #include "policies.h" #include "rephist.h" #include "router.h" +#include "routerkeys.h" #include "routerlist.h" #include "routerparse.h" #include "entrynodes.h" /* needed for guardfraction methods */ #include "torcert.h" +#include "shared_random_state.h" /** * \file dirvote.c @@ -73,6 +75,7 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, char digest[DIGEST_LEN]; uint32_t addr; char *client_versions_line = NULL, *server_versions_line = NULL; + char *shared_random_vote_str = NULL; networkstatus_voter_info_t *voter; char *status = NULL; @@ -114,6 +117,9 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, packages = tor_strdup(""); } + /* Get shared random commitments/reveals line(s). */ + shared_random_vote_str = sr_get_string_for_vote(); + { char published[ISO_TIME_LEN+1]; char va[ISO_TIME_LEN+1]; @@ -153,7 +159,8 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, "flag-thresholds %s\n" "params %s\n" "dir-source %s %s %s %s %d %d\n" - "contact %s\n", + "contact %s\n" + "%s", /* shared randomness information */ v3_ns->type == NS_TYPE_VOTE ? "vote" : "opinion", methods, published, va, fu, vu, @@ -166,12 +173,15 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, params, voter->nickname, fingerprint, voter->address, fmt_addr32(addr), voter->dir_port, voter->or_port, - voter->contact); + voter->contact, + shared_random_vote_str ? + shared_random_vote_str : ""); tor_free(params); tor_free(flags); tor_free(flag_thresholds); tor_free(methods); + tor_free(shared_random_vote_str); if (!tor_digest_is_zero(voter->legacy_id_digest)) { char fpbuf[HEX_DIGEST_LEN+1]; @@ -1300,6 +1310,14 @@ networkstatus_compute_consensus(smartlist_t *votes, smartlist_add(chunks, tor_strdup("\n")); } + if (consensus_method >= MIN_METHOD_FOR_SHARED_RANDOM) { + /* Add the shared random value. */ + char *srv_lines = sr_get_string_for_consensus(votes); + if (srv_lines != NULL) { + smartlist_add(chunks, srv_lines); + } + } + /* Sort the votes. */ smartlist_sort(votes, compare_votes_by_authority_id_); /* Add the authority sections. */ diff --git a/src/or/dirvote.h b/src/or/dirvote.h index f2080a522e..4c563aa5b2 100644 --- a/src/or/dirvote.h +++ b/src/or/dirvote.h @@ -55,7 +55,7 @@ #define MIN_SUPPORTED_CONSENSUS_METHOD 13 /** The highest consensus method that we currently support. */ -#define MAX_SUPPORTED_CONSENSUS_METHOD 22 +#define MAX_SUPPORTED_CONSENSUS_METHOD 23 /** Lowest consensus method where microdesc consensuses omit any entry * with no microdesc. */ @@ -90,10 +90,15 @@ * ed25519 identities in microdescriptors. (Broken; see * consensus_method_is_supported() for more info.) */ #define MIN_METHOD_FOR_ED25519_ID_IN_MD 21 + /** Lowest consensus method where authorities vote on ed25519 ids and ensure * ed25519 id consistency. */ #define MIN_METHOD_FOR_ED25519_ID_VOTING 22 +/** Lowest consensus method where authorities may include a shared random + * value(s). */ +#define MIN_METHOD_FOR_SHARED_RANDOM 23 + /** Default bandwidth to clip unmeasured bandwidths to using method >= * MIN_METHOD_TO_CLIP_UNMEASURED_BW. (This is not a consensus method; do not * get confused with the above macros.) */ diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index da51698da4..aabbffff1d 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -32,7 +32,9 @@ #include "router.h" #include "routerlist.h" #include "routerparse.h" +#include "shared_random.h" #include "transports.h" +#include "torcert.h" /** Map from lowercase nickname to identity digest of named server, if any. */ static strmap_t *named_server_map = NULL; @@ -320,6 +322,14 @@ networkstatus_vote_free(networkstatus_t *ns) digestmap_free(ns->desc_digest_map, NULL); + if (ns->sr_info.commits) { + SMARTLIST_FOREACH(ns->sr_info.commits, sr_commit_t *, c, + sr_commit_free(c)); + smartlist_free(ns->sr_info.commits); + } + tor_free(ns->sr_info.previous_srv); + tor_free(ns->sr_info.current_srv); + memwipe(ns, 11, sizeof(*ns)); tor_free(ns); } diff --git a/src/or/or.h b/src/or/or.h index 5a19f9a944..1b7f1a8568 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -2505,6 +2505,18 @@ typedef struct networkstatus_voter_info_t { smartlist_t *sigs; } networkstatus_voter_info_t; +typedef struct networkstatus_sr_info_t { + /* Indicate if the dirauth partitipates in the SR protocol with its vote. + * This is tied to the SR flag in the vote. */ + unsigned int participate:1; + /* Both vote and consensus: Current and previous SRV. If list is empty, + * this means none were found in either the consensus or vote. */ + struct sr_srv_t *previous_srv; + struct sr_srv_t *current_srv; + /* Vote only: List of commitments. */ + smartlist_t *commits; +} networkstatus_sr_info_t; + /** Enumerates the possible seriousness values of a networkstatus document. */ typedef enum { NS_TYPE_VOTE, @@ -2587,6 +2599,9 @@ typedef struct networkstatus_t { /** If present, a map from descriptor digest to elements of * routerstatus_list. */ digestmap_t *desc_digest_map; + + /** Contains the shared random protocol data from a vote or consensus. */ + networkstatus_sr_info_t sr_info; } networkstatus_t; /** A set of signatures for a networkstatus consensus. Unless otherwise @@ -4480,6 +4495,12 @@ typedef struct { /** Autobool: Do we try to retain capabilities if we can? */ int KeepBindCapabilities; + + /** Bool (default: 1): Switch for the shared random protocol. Only + * relevant to a directory authority. If off, the authority won't + * participate in the protocol. If on (default), a flag is added to the + * vote indicating participation. */ + int AuthDirSharedRandomness; } or_options_t; /** Persistent state for an onion router, as saved to disk. */ diff --git a/src/or/shared_random.c b/src/or/shared_random.c index dd567bc06a..0f988af50a 100644 --- a/src/or/shared_random.c +++ b/src/or/shared_random.c @@ -14,12 +14,19 @@ #include "shared_random.h" #include "config.h" #include "confparse.h" +#include "dirvote.h" #include "networkstatus.h" #include "routerkeys.h" #include "router.h" #include "routerlist.h" #include "shared_random_state.h" +/* String prefix of shared random values in votes/consensuses. */ +static const char previous_srv_str[] = "shared-rand-previous-value"; +static const char current_srv_str[] = "shared-rand-current-value"; +static const char commit_ns_str[] = "shared-rand-commit"; +static const char sr_flag_ns_str[] = "shared-rand-participate"; + /* Allocate a new commit object and initializing it with identity * that MUST be provided. The digest algorithm is set to the default one * that is supported. The rest is uninitialized. This never returns NULL. */ @@ -307,6 +314,216 @@ compare_reveal_(const void **_a, const void **_b) sizeof(a->hashed_reveal)); } +/* Given commit give the line that we should place in our votes. + * It's the responsibility of the caller to free the string. */ +static char * +get_vote_line_from_commit(const sr_commit_t *commit, sr_phase_t phase) +{ + char *vote_line = NULL; + + switch (phase) { + case SR_PHASE_COMMIT: + tor_asprintf(&vote_line, "%s %s %s %s\n", + commit_ns_str, + crypto_digest_algorithm_get_name(commit->alg), + commit->rsa_identity_fpr, + commit->encoded_commit); + break; + case SR_PHASE_REVEAL: + { + /* Send a reveal value for this commit if we have one. */ + const char *reveal_str = commit->encoded_reveal; + if (tor_mem_is_zero(commit->encoded_reveal, + sizeof(commit->encoded_reveal))) { + reveal_str = ""; + } + tor_asprintf(&vote_line, "%s %s %s %s %s\n", + commit_ns_str, + crypto_digest_algorithm_get_name(commit->alg), + commit->rsa_identity_fpr, + commit->encoded_commit, reveal_str); + break; + } + default: + tor_assert(0); + } + + log_debug(LD_DIR, "SR: Commit vote line: %s", vote_line); + return vote_line; +} + +/* Return a heap allocated string that contains the given srv string + * representation formatted for a networkstatus document using the + * key as the start of the line. This doesn't return NULL. */ +static char * +srv_to_ns_string(const sr_srv_t *srv, const char *key) +{ + char *srv_str; + char srv_hash_encoded[SR_SRV_VALUE_BASE64_LEN + 1]; + tor_assert(srv); + tor_assert(key); + + sr_srv_encode(srv_hash_encoded, srv); + tor_asprintf(&srv_str, "%s %d %s\n", key, + srv->num_reveals, srv_hash_encoded); + log_debug(LD_DIR, "SR: Consensus SRV line: %s", srv_str); + return srv_str; +} + +/* Given the previous SRV and the current SRV, return a heap allocated + * string with their data that could be put in a vote or a consensus. Caller + * must free the returned string. Return NULL if no SRVs were provided. */ +static char * +get_ns_str_from_sr_values(const sr_srv_t *prev_srv, const sr_srv_t *cur_srv) +{ + smartlist_t *chunks = NULL; + char *srv_str; + + if (!prev_srv && !cur_srv) { + return NULL; + } + + chunks = smartlist_new(); + + if (prev_srv) { + char *srv_line = srv_to_ns_string(prev_srv, previous_srv_str); + smartlist_add(chunks, srv_line); + } + + if (cur_srv) { + char *srv_line = srv_to_ns_string(cur_srv, current_srv_str); + smartlist_add(chunks, srv_line); + } + + /* Join the line(s) here in one string to return. */ + srv_str = smartlist_join_strings(chunks, "", 0, NULL); + SMARTLIST_FOREACH(chunks, char *, s, tor_free(s)); + smartlist_free(chunks); + + return srv_str; +} + +/* Return the number of required participants of the SR protocol. This is + * based on a consensus params. */ +static int +get_n_voters_for_srv_agreement(void) +{ + int num_dirauths = get_n_authorities(V3_DIRINFO); + /* If the params is not found, default value should always be the maximum + * number of trusted authorities. Let's not take any chances. */ + return networkstatus_get_param(NULL, "AuthDirNumSRVAgreements", + num_dirauths, 1, num_dirauths); +} + +/* Return 1 if we should we keep an SRV voted by n_agreements auths. + * Return 0 if we should ignore it. */ +static int +should_keep_srv(int n_agreements) +{ + /* Check if the most popular SRV has reached majority. */ + int n_voters = get_n_authorities(V3_DIRINFO); + int votes_required_for_majority = (n_voters / 2) + 1; + + /* We need at the very least majority to keep a value. */ + if (n_agreements < votes_required_for_majority) { + log_notice(LD_DIR, "SR: SRV didn't reach majority [%d/%d]!", + n_agreements, votes_required_for_majority); + return 0; + } + + /* When we just computed a new SRV, we need to have super majority in order + * to keep it. */ + if (sr_state_srv_is_fresh()) { + /* Check if we have super majority for this new SRV value. */ + int num_required_agreements = get_n_voters_for_srv_agreement(); + + if (n_agreements < num_required_agreements) { + log_notice(LD_DIR, "SR: New SRV didn't reach agreement [%d/%d]!", + n_agreements, num_required_agreements); + return 0; + } + } + + return 1; +} + +/* Helper: compare two DIGEST256_LEN digests. */ +static int +compare_srvs_(const void **_a, const void **_b) +{ + const sr_srv_t *a = *_a, *b = *_b; + return tor_memcmp(a->value, b->value, sizeof(a->value)); +} + +/* Return the most frequent member of the sorted list of DIGEST256_LEN + * digests in sl with the count of that most frequent element. */ +static sr_srv_t * +smartlist_get_most_frequent_srv(const smartlist_t *sl, int *count_out) +{ + return smartlist_get_most_frequent_(sl, compare_srvs_, count_out); +} + +/* Using a list of votes, return the SRV object from them that has + * been voted by the majority of dirauths. If current is set, we look + * for the current SRV value else the previous one. The returned pointer is + * an object located inside a vote. NULL is returned if no appropriate value + * could be found. */ +STATIC sr_srv_t * +get_majority_srv_from_votes(const smartlist_t *votes, int current) +{ + int count = 0; + sr_srv_t *most_frequent_srv = NULL; + sr_srv_t *the_srv = NULL; + smartlist_t *srv_list; + + tor_assert(votes); + + srv_list = smartlist_new(); + + /* Walk over votes and register any SRVs found. */ + SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, v) { + sr_srv_t *srv_tmp = NULL; + + if (!v->sr_info.participate) { + /* Ignore vote that do not participate. */ + continue; + } + /* Do we want previous or current SRV? */ + srv_tmp = current ? v->sr_info.current_srv : v->sr_info.previous_srv; + if (!srv_tmp) { + continue; + } + + smartlist_add(srv_list, srv_tmp); + } SMARTLIST_FOREACH_END(v); + + most_frequent_srv = smartlist_get_most_frequent_srv(srv_list, &count); + if (!most_frequent_srv) { + goto end; + } + + /* Was this SRV voted by enough auths for us to keep it? */ + if (!should_keep_srv(count)) { + goto end; + } + + /* We found an SRV that we can use! Habemus SRV! */ + the_srv = most_frequent_srv; + + { + /* Debugging */ + char encoded[SR_SRV_VALUE_BASE64_LEN + 1]; + sr_srv_encode(encoded, the_srv); + log_debug(LD_DIR, "SR: Chosen SRV by majority: %s (%d votes)", encoded, + count); + } + + end: + /* We do not free any sr_srv_t values, we don't have the ownership. */ + smartlist_free(srv_list); + return the_srv; +} + /* Encode the given shared random value and put it in dst. Destination * buffer must be at least SR_SRV_VALUE_BASE64_LEN plus the NULL byte. */ void @@ -572,6 +789,90 @@ sr_parse_commit(const smartlist_t *args) return NULL; } +/* Return a heap-allocated string containing commits that should be put in + * the votes. It's the responsibility of the caller to free the string. + * This always return a valid string, either empty or with line(s). */ +char * +sr_get_string_for_vote(void) +{ + char *vote_str = NULL; + digestmap_t *state_commits; + smartlist_t *chunks = smartlist_new(); + const or_options_t *options = get_options(); + + /* Are we participating in the protocol? */ + if (!options->AuthDirSharedRandomness) { + goto end; + } + + log_debug(LD_DIR, "SR: Preparing our vote info:"); + + /* First line, put in the vote the participation flag. */ + { + char *sr_flag_line; + tor_asprintf(&sr_flag_line, "%s\n", sr_flag_ns_str); + smartlist_add(chunks, sr_flag_line); + } + + /* In our vote we include every commitment in our permanent state. */ + state_commits = sr_state_get_commits(); + DIGESTMAP_FOREACH(state_commits, key, const sr_commit_t *, commit) { + char *line = get_vote_line_from_commit(commit, sr_state_get_phase()); + smartlist_add(chunks, line); + } DIGESTMAP_FOREACH_END; + + /* Add the SRV value(s) if any. */ + { + char *srv_lines = get_ns_str_from_sr_values(sr_state_get_previous_srv(), + sr_state_get_current_srv()); + if (srv_lines) { + smartlist_add(chunks, srv_lines); + } + } + + end: + vote_str = smartlist_join_strings(chunks, "", 0, NULL); + SMARTLIST_FOREACH(chunks, char *, s, tor_free(s)); + smartlist_free(chunks); + return vote_str; +} + +/* Return a heap-allocated string that should be put in the consensus and + * contains the shared randomness values. It's the responsibility of the + * caller to free the string. NULL is returned if no SRV(s) available. + * + * This is called when a consensus (any flavor) is bring created thus it + * should NEVER change the state nor the state should be changed in between + * consensus creation. */ +char * +sr_get_string_for_consensus(const smartlist_t *votes) +{ + char *srv_str; + const or_options_t *options = get_options(); + + tor_assert(votes); + + /* Not participating, avoid returning anything. */ + if (!options->AuthDirSharedRandomness) { + log_info(LD_DIR, "SR: Support disabled (AuthDirSharedRandomness %d)", + options->AuthDirSharedRandomness); + goto end; + } + + /* Check the votes and figure out if SRVs should be included in the final + * consensus. */ + sr_srv_t *prev_srv = get_majority_srv_from_votes(votes, 0); + sr_srv_t *cur_srv = get_majority_srv_from_votes(votes, 1); + srv_str = get_ns_str_from_sr_values(prev_srv, cur_srv); + if (!srv_str) { + goto end; + } + + return srv_str; + end: + return NULL; +} + /* Initialize shared random subsystem. This MUST be called early in the boot * process of tor. Return 0 on success else -1 on error. */ int diff --git a/src/or/shared_random.h b/src/or/shared_random.h index 878bfbff45..f1fbe273f1 100644 --- a/src/or/shared_random.h +++ b/src/or/shared_random.h @@ -101,13 +101,15 @@ typedef struct sr_commit_t { int sr_init(int save_to_disk); void sr_save_and_cleanup(void); +sr_commit_t *sr_parse_commit(const smartlist_t *args); +sr_srv_t *sr_parse_srv(const smartlist_t *args); +char *sr_get_string_for_vote(void); +char *sr_get_string_for_consensus(const smartlist_t *votes); void sr_commit_free(sr_commit_t *commit); void sr_srv_encode(char *dst, const sr_srv_t *srv); /* Private methods (only used by shared_random_state.c): */ -sr_commit_t *sr_parse_commit(const smartlist_t *args); -sr_srv_t *sr_parse_srv(const smartlist_t *args); void sr_compute_srv(void); sr_commit_t *sr_generate_our_commit(time_t timestamp, const authority_cert_t *my_rsa_cert); @@ -123,6 +125,9 @@ STATIC int reveal_decode(const char *encoded, sr_commit_t *commit); STATIC int commit_has_reveal_value(const sr_commit_t *commit); +STATIC sr_srv_t *get_majority_srv_from_votes(const smartlist_t *votes, + int current); + #endif /* SHARED_RANDOM_PRIVATE */ #endif /* TOR_SHARED_RANDOM_H */ From 0c26a6db7e683c23505c1c9187fd538c7b80ce31 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 3 May 2016 11:36:09 -0400 Subject: [PATCH 05/26] prop250: Parse votes and consensus One of the last piece that parses the votes and consensus in order to update our state and make decision for the SR values. We need to inform the SR subsystem when we set the current consensus because this can be called when loaded from file or downloaded from other authorities or computed. The voting schedule is used for the SR timings since we are bound to the voting system. Signed-off-by: David Goulet Signed-off-by: George Kadianakis --- src/or/directory.c | 5 + src/or/dirvote.c | 7 + src/or/routerparse.c | 158 ++++++++++++++ src/or/shared_random.c | 395 ++++++++++++++++++++++++++++++++++- src/or/shared_random.h | 16 ++ src/or/shared_random_state.c | 29 +++ src/or/shared_random_state.h | 1 + 7 files changed, 609 insertions(+), 2 deletions(-) diff --git a/src/or/directory.c b/src/or/directory.c index 876eaa4415..b32090aec7 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -30,6 +30,7 @@ #include "routerlist.h" #include "routerparse.h" #include "routerset.h" +#include "shared_random.h" #if defined(EXPORTMALLINFO) && defined(HAVE_MALLOC_H) && defined(HAVE_MALLINFO) #ifndef OPENBSD @@ -2026,6 +2027,10 @@ connection_dir_client_reached_eof(dir_connection_t *conn) update_microdescs_from_networkstatus(now); update_microdesc_downloads(now); directory_info_has_arrived(now, 0, 0); + if (authdir_mode_v3(get_options())) { + sr_act_post_consensus( + networkstatus_get_latest_consensus_by_flavor(FLAV_NS)); + } log_info(LD_DIR, "Successfully loaded consensus."); } diff --git a/src/or/dirvote.c b/src/or/dirvote.c index af093deaf4..4eccdd0611 100644 --- a/src/or/dirvote.c +++ b/src/or/dirvote.c @@ -2668,6 +2668,9 @@ dirvote_act(const or_options_t *options, time_t now) dirvote_publish_consensus(); dirvote_clear_votes(0); voting_schedule.have_published_consensus = 1; + /* Update our shared random state with the consensus just published. */ + sr_act_post_consensus( + networkstatus_get_latest_consensus_by_flavor(FLAV_NS)); /* XXXX We will want to try again later if we haven't got enough * signatures yet. Implement this if it turns out to ever happen. */ dirvote_recalculate_timing(options, now); @@ -3007,6 +3010,10 @@ dirvote_add_vote(const char *vote_body, const char **msg_out, int *status_out) } } SMARTLIST_FOREACH_END(v); + /* This a valid vote, update our shared random state. */ + sr_handle_received_commits(vote->sr_info.commits, + vote->cert->identity_key); + pending_vote = tor_malloc_zero(sizeof(pending_vote_t)); pending_vote->vote_body = new_cached_dir(tor_strndup(vote_body, end_of_vote-vote_body), diff --git a/src/or/routerparse.c b/src/or/routerparse.c index 130f9f4aa1..88cfbdc4f1 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -28,6 +28,7 @@ #include "routerparse.h" #include "entrynodes.h" #include "torcert.h" +#include "shared_random.h" #undef log #include @@ -145,6 +146,11 @@ typedef enum { K_CONSENSUS_METHOD, K_LEGACY_DIR_KEY, K_DIRECTORY_FOOTER, + K_SIGNING_CERT_ED, + K_SR_FLAG, + K_COMMIT, + K_PREVIOUS_SRV, + K_CURRENT_SRV, K_PACKAGE, A_PURPOSE, @@ -446,6 +452,11 @@ static token_rule_t networkstatus_token_table[] = { T1("known-flags", K_KNOWN_FLAGS, ARGS, NO_OBJ ), T01("params", K_PARAMS, ARGS, NO_OBJ ), T( "fingerprint", K_FINGERPRINT, CONCAT_ARGS, NO_OBJ ), + T01("signing-ed25519", K_SIGNING_CERT_ED, NO_ARGS , NEED_OBJ ), + T01("shared-rand-participate",K_SR_FLAG, NO_ARGS, NO_OBJ ), + T0N("shared-rand-commit", K_COMMIT, GE(3), NO_OBJ ), + T01("shared-rand-previous-value", K_PREVIOUS_SRV,EQ(2), NO_OBJ ), + T01("shared-rand-current-value", K_CURRENT_SRV, EQ(2), NO_OBJ ), T0N("package", K_PACKAGE, CONCAT_ARGS, NO_OBJ ), CERTIFICATE_MEMBERS @@ -485,6 +496,9 @@ static token_rule_t networkstatus_consensus_token_table[] = { T01("consensus-method", K_CONSENSUS_METHOD, EQ(1), NO_OBJ), T01("params", K_PARAMS, ARGS, NO_OBJ ), + T01("shared-rand-previous-value", K_PREVIOUS_SRV, EQ(2), NO_OBJ ), + T01("shared-rand-current-value", K_CURRENT_SRV, EQ(2), NO_OBJ ), + END_OF_TABLE }; @@ -2843,6 +2857,134 @@ networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method) return valid; } +/** Parse and extract all SR commits from tokens and place them in + * ns. */ +static void +extract_shared_random_commits(networkstatus_t *ns, smartlist_t *tokens) +{ + smartlist_t *chunks = NULL; + + tor_assert(ns); + tor_assert(tokens); + /* Commits are only present in a vote. */ + tor_assert(ns->type == NS_TYPE_VOTE); + + ns->sr_info.commits = smartlist_new(); + + smartlist_t *commits = find_all_by_keyword(tokens, K_COMMIT); + /* It's normal that a vote might contain no commits even if it participates + * in the SR protocol. Don't treat it as an error. */ + if (commits == NULL) { + goto end; + } + + /* Parse the commit. We do NO validation of number of arguments or ordering + * for forward compatibility, it's the parse commit job to inform us if it's + * supported or not. */ + chunks = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(commits, directory_token_t *, tok) { + /* Extract all arguments and put them in the chunks list. */ + for (int i = 0; i < tok->n_args; i++) { + smartlist_add(chunks, tok->args[i]); + } + sr_commit_t *commit = sr_parse_commit(chunks); + smartlist_clear(chunks); + if (commit == NULL) { + /* Get voter identity so we can warn that this dirauth vote contains + * commit we can't parse. */ + networkstatus_voter_info_t *voter = smartlist_get(ns->voters, 0); + tor_assert(voter); + log_warn(LD_DIR, "SR: Unable to parse commit %s from vote of voter %s.", + escaped(tok->object_body), + hex_str(voter->identity_digest, + sizeof(voter->identity_digest))); + /* Commitment couldn't be parsed. Continue onto the next commit because + * this one could be unsupported for instance. */ + continue; + } + /* Add newly created commit object to the vote. */ + smartlist_add(ns->sr_info.commits, commit); + } SMARTLIST_FOREACH_END(tok); + + end: + smartlist_free(chunks); + smartlist_free(commits); +} + +/** Check if a shared random value of type srv_type is in + * tokens. If there is, parse it and set it to srv_out. Return + * -1 on failure, 0 on success. The resulting srv is allocated on the heap and + * it's the responsibility of the caller to free it. */ +static int +extract_one_srv(smartlist_t *tokens, directory_keyword srv_type, + sr_srv_t **srv_out) +{ + int ret = -1; + directory_token_t *tok; + sr_srv_t *srv = NULL; + smartlist_t *chunks; + + tor_assert(tokens); + + chunks = smartlist_new(); + tok = find_opt_by_keyword(tokens, srv_type); + if (!tok) { + /* That's fine, no SRV is allowed. */ + ret = 0; + goto end; + } + for (int i = 0; i < tok->n_args; i++) { + smartlist_add(chunks, tok->args[i]); + } + srv = sr_parse_srv(chunks); + if (srv == NULL) { + log_warn(LD_DIR, "SR: Unparseable SRV %s", escaped(tok->object_body)); + goto end; + } + /* All is good. */ + *srv_out = srv; + ret = 0; + end: + smartlist_free(chunks); + return ret; +} + +/** Extract any shared random values found in tokens and place them in + * the networkstatus ns. */ +static void +extract_shared_random_srvs(networkstatus_t *ns, smartlist_t *tokens) +{ + const char *voter_identity; + networkstatus_voter_info_t *voter; + + tor_assert(ns); + tor_assert(tokens); + /* Can be only one of them else code flow. */ + tor_assert(ns->type == NS_TYPE_VOTE || ns->type == NS_TYPE_CONSENSUS); + + if (ns->type == NS_TYPE_VOTE) { + voter = smartlist_get(ns->voters, 0); + tor_assert(voter); + voter_identity = hex_str(voter->identity_digest, + sizeof(voter->identity_digest)); + } else { + /* Consensus has multiple voters so no specific voter. */ + voter_identity = "consensus"; + } + + /* We extract both and on error, everything is stopped because it means + * the votes is malformed for the shared random value(s). */ + if (extract_one_srv(tokens, K_PREVIOUS_SRV, &ns->sr_info.previous_srv) < 0) { + log_warn(LD_DIR, "SR: Unable to parse previous SRV from %s", + voter_identity); + /* Maybe we have a chance with the current SRV so let's try it anyway. */ + } + if (extract_one_srv(tokens, K_CURRENT_SRV, &ns->sr_info.current_srv) < 0) { + log_warn(LD_DIR, "SR: Unable to parse current SRV from %s", + voter_identity); + } +} + /** Parse a v3 networkstatus vote, opinion, or consensus (depending on * ns_type), from s, and return the result. Return NULL on failure. */ networkstatus_t * @@ -3187,6 +3329,22 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out, } } + /* If this is a vote document, check if information about the shared + randomness protocol is included, and extract it. */ + if (ns->type == NS_TYPE_VOTE) { + /* Does this authority participates in the SR protocol? */ + tok = find_opt_by_keyword(tokens, K_SR_FLAG); + if (tok) { + ns->sr_info.participate = 1; + /* Get the SR commitments and reveals from the vote. */ + extract_shared_random_commits(ns, tokens); + } + } + /* For both a vote and consensus, extract the shared random values. */ + if (ns->type == NS_TYPE_VOTE || ns->type == NS_TYPE_CONSENSUS) { + extract_shared_random_srvs(ns, tokens); + } + /* Parse routerstatus lines. */ rs_tokens = smartlist_new(); rs_area = memarea_new(); diff --git a/src/or/shared_random.c b/src/or/shared_random.c index 0f988af50a..e3fe1a9e73 100644 --- a/src/or/shared_random.c +++ b/src/or/shared_random.c @@ -6,6 +6,83 @@ * * \brief Functions and data structure needed to accomplish the shared * random protocol as defined in proposal #250. + * + * \details + * + * This file implements the dirauth-only commit-and-reveal protocol specified + * by proposal #250. The protocol has two phases (sr_phase_t): the commitment + * phase and the reveal phase (see get_sr_protocol_phase()). + * + * During the protocol, directory authorities keep state in memory (using + * sr_state_t) and in disk (using sr_disk_state_t). The synchronization between + * these two data structures happens in disk_state_update() and + * disk_state_parse(). + * + * Here is a rough protocol outline: + * + * 1) In the beginning of the commitment phase, dirauths generate a + * commitment/reveal value for the current protocol run (see + * new_protocol_run() and sr_generate_our_commit()). + * + * 2) During voting, dirauths publish their commits in their votes + * depending on the current phase. Dirauths also include the two + * latest shared random values (SRV) in their votes. + * (see sr_get_string_for_vote()) + * + * 3) Upon receiving a commit from a vote, authorities parse it, verify + * it, and attempt to save any new commitment or reveal information in + * their state file (see extract_shared_random_commits() and + * sr_handle_received_commits()). They also parse SRVs from votes to + * decide which SRV should be included in the final consensus (see + * extract_shared_random_srvs()). + * + * 3) After voting is done, we count the SRVs we extracted from the votes, + * to find the one voted by the majority of dirauths which should be + * included in the final consensus (see get_majority_srv_from_votes()). + * If an appropriate SRV is found, it is embedded in the consensus (see + * sr_get_string_for_consensus()). + * + * 4) At the end of the reveal phase, dirauths compute a fresh SRV for the + * day using the active commits (see sr_compute_srv()). This new SRV + * is embedded in the votes as described above. + * + * Some more notes: + * + * - To support rebooting authorities and to avoid double voting, each dirauth + * saves the current state of the protocol on disk so that it can resume + * normally in case of reboot. The disk state (sr_disk_state_t) is managed by + * shared_random_state.c:state_query() and we go to extra lengths to ensure + * that the state is flushed on disk everytime we receive any useful + * information like commits or SRVs. + * + * - When we receive a commit from a vote, we examine it to see if it's useful + * to us and whether it's appropriate to receive it according to the current + * phase of the protocol (see should_keep_commit()). If the commit is useful + * to us, we save it in our disk state using save_commit_to_state(). When we + * receive the reveal information corresponding to a commitment, we verify + * that they indeed match using verify_commit_and_reveal(). + * + * - We treat consensuses as the ground truth, so everytime we generate a new + * consensus we update our SR state accordingly even if our local view was + * different (see sr_act_post_consensus()). + * + * - After a consensus has been composed, the SR protocol state gets prepared + * for the next voting session using sr_state_update(). That function takes + * care of housekeeping and also rotates the SRVs and commits in case a new + * protocol run is coming up. We also call sr_state_update() on bootup (in + * sr_state_init()), to prepare the state for the very first voting session. + * + * Terminology: + * + * - "Commitment" is the commitment value of the commit-and-reveal protocol. + * + * - "Reveal" is the reveal value of the commit-and-reveal protocol. + * + * - "Commit" is a struct (sr_commit_t) that contains a commitment value and + * optionally also a corresponding reveal value. + * + * - "SRV" is the Shared Random Value that gets generated as the result of the + * commit-and-reveal protocol. **/ #define SHARED_RANDOM_PRIVATE @@ -27,6 +104,22 @@ static const char current_srv_str[] = "shared-rand-current-value"; static const char commit_ns_str[] = "shared-rand-commit"; static const char sr_flag_ns_str[] = "shared-rand-participate"; +/* Return a heap allocated copy of the SRV orig. */ +STATIC sr_srv_t * +srv_dup(const sr_srv_t *orig) +{ + sr_srv_t *dup = NULL; + + if (!orig) { + return NULL; + } + + dup = tor_malloc_zero(sizeof(sr_srv_t)); + dup->num_reveals = orig->num_reveals; + memcpy(dup->value, orig->value, sizeof(dup->value)); + return dup; +} + /* Allocate a new commit object and initializing it with identity * that MUST be provided. The digest algorithm is set to the default one * that is supported. The rest is uninitialized. This never returns NULL. */ @@ -65,6 +158,59 @@ commit_log(const sr_commit_t *commit) } } +/* Make sure that the commitment and reveal information in commit + * match. If they match return 0, return -1 otherwise. This function MUST be + * used everytime we receive a new reveal value. Furthermore, the commit + * object MUST have a reveal value and the hash of the reveal value. */ +STATIC int +verify_commit_and_reveal(const sr_commit_t *commit) +{ + tor_assert(commit); + + log_debug(LD_DIR, "SR: Validating commit from authority %s", + commit->rsa_identity_fpr); + + /* Check that the timestamps match. */ + if (commit->commit_ts != commit->reveal_ts) { + log_warn(LD_BUG, "SR: Commit timestamp %ld doesn't match reveal " + "timestamp %ld", commit->commit_ts, commit->reveal_ts); + goto invalid; + } + + /* Verify that the hashed_reveal received in the COMMIT message, matches + * the reveal we just received. */ + { + /* We first hash the reveal we just received. */ + char received_hashed_reveal[sizeof(commit->hashed_reveal)]; + + /* Only sha3-256 is supported. */ + if (commit->alg != SR_DIGEST_ALG) { + goto invalid; + } + + /* Use the invariant length since the encoded reveal variable has an + * extra byte for the NUL terminated byte. */ + if (crypto_digest256(received_hashed_reveal, commit->encoded_reveal, + SR_REVEAL_BASE64_LEN, commit->alg)) { + /* Unable to digest the reveal blob, this is unlikely. */ + goto invalid; + } + + /* Now compare that with the hashed_reveal we received in COMMIT. */ + if (fast_memneq(received_hashed_reveal, commit->hashed_reveal, + sizeof(received_hashed_reveal))) { + log_warn(LD_BUG, "SR: Received reveal value from authority %s " + "does't match the commit value.", + commit->rsa_identity_fpr); + goto invalid; + } + } + + return 0; + invalid: + return -1; +} + /* Return true iff the commit contains an encoded reveal value. */ STATIC int commit_has_reveal_value(const sr_commit_t *commit) @@ -403,8 +549,176 @@ get_ns_str_from_sr_values(const sr_srv_t *prev_srv, const sr_srv_t *cur_srv) return srv_str; } -/* Return the number of required participants of the SR protocol. This is - * based on a consensus params. */ +/* Return 1 iff the two commits have the same commitment values. This + * function does not care about reveal values. */ +STATIC int +commitments_are_the_same(const sr_commit_t *commit_one, + const sr_commit_t *commit_two) +{ + tor_assert(commit_one); + tor_assert(commit_two); + + if (strcmp(commit_one->encoded_commit, commit_two->encoded_commit)) { + return 0; + } + return 1; +} + +/* We just received a commit from the vote of authority with + * identity_digest. Return 1 if this commit is authorititative that + * is, it belongs to the authority that voted it. Else return 0 if not. */ +STATIC int +commit_is_authoritative(const sr_commit_t *commit, + const char *voter_key) +{ + tor_assert(commit); + tor_assert(voter_key); + + return !strcmp(commit->rsa_identity_fpr, voter_key); +} + +/* Decide if the newly received commit should be kept depending on + * the current phase and state of the protocol. The voter_key is the + * RSA identity key fingerprint of the authority's vote from which the + * commit comes from. The phase is the phase we should be validating + * the commit for. Return 1 if the commit should be added to our state or 0 + * if not. */ +STATIC int +should_keep_commit(const sr_commit_t *commit, const char *voter_key, + sr_phase_t phase) +{ + sr_commit_t *saved_commit; + + tor_assert(commit); + tor_assert(voter_key); + + log_debug(LD_DIR, "SR: Inspecting commit from %s (voter: %s)?", + commit->rsa_identity_fpr, voter_key); + + /* For a commit to be considered, it needs to be authoritative (it should + * be the voter's own commit). */ + if (!commit_is_authoritative(commit, voter_key)) { + log_debug(LD_DIR, "SR: Ignoring non-authoritative commit."); + goto ignore; + } + + /* Check if the authority that voted for commit has already posted + * a commit before. */ + saved_commit = sr_state_get_commit(commit->rsa_identity_fpr); + + switch (phase) { + case SR_PHASE_COMMIT: + /* Already having a commit for an authority so ignore this one. */ + if (saved_commit) { + log_debug(LD_DIR, "SR: Ignoring known commit during COMMIT phase."); + goto ignore; + } + + /* A commit with a reveal value during commitment phase is very wrong. */ + if (commit_has_reveal_value(commit)) { + log_warn(LD_DIR, "SR: Commit from authority %s has a reveal value " + "during COMMIT phase. (voter: %s)", + commit->rsa_identity_fpr, voter_key); + goto ignore; + } + break; + case SR_PHASE_REVEAL: + /* We are now in reveal phase. We keep a commit if and only if: + * + * - We have already seen a commit by this auth, AND + * - the saved commit has the same commitment value as this one, AND + * - the saved commit has no reveal information, AND + * - this commit does have reveal information, AND + * - the reveal & commit information are matching. + * + * If all the above are true, then we are interested in this new commit + * for its reveal information. */ + + if (!saved_commit) { + log_debug(LD_DIR, "SR: Ignoring commit first seen in reveal phase."); + goto ignore; + } + + if (!commitments_are_the_same(commit, saved_commit)) { + log_warn(LD_DIR, "SR: Commit from authority %s is different from " + "previous commit in our state (voter: %s)", + commit->rsa_identity_fpr, voter_key); + goto ignore; + } + + if (commit_has_reveal_value(saved_commit)) { + log_debug(LD_DIR, "SR: Ignoring commit with known reveal info."); + goto ignore; + } + + if (!commit_has_reveal_value(commit)) { + log_debug(LD_DIR, "SR: Ignoring commit without reveal value."); + goto ignore; + } + + if (verify_commit_and_reveal(commit) < 0) { + log_warn(LD_BUG, "SR: Commit from authority %s has an invalid " + "reveal value. (voter: %s)", + commit->rsa_identity_fpr, voter_key); + goto ignore; + } + break; + default: + tor_assert(0); + } + + return 1; + + ignore: + return 0; +} + +/* We are in reveal phase and we found a valid and verified commit in + * a vote that contains reveal values that we could use. Update the commit + * we have in our state. Never call this with an unverified commit. */ +STATIC void +save_commit_during_reveal_phase(const sr_commit_t *commit) +{ + sr_commit_t *saved_commit; + + tor_assert(commit); + + /* Get the commit from our state. */ + saved_commit = sr_state_get_commit(commit->rsa_identity_fpr); + tor_assert(saved_commit); + /* Safety net. They can not be different commitments at this point. */ + int same_commits = commitments_are_the_same(commit, saved_commit); + tor_assert(same_commits); + + /* Copy reveal information to our saved commit. */ + sr_state_copy_reveal_info(saved_commit, commit); +} + +/* Save commit to our persistent state. Depending on the current + * phase, different actions are taken. Steals reference of commit. + * The commit object MUST be valid and verified before adding it to the + * state. */ +STATIC void +save_commit_to_state(sr_commit_t *commit) +{ + sr_phase_t phase = sr_state_get_phase(); + + switch (phase) { + case SR_PHASE_COMMIT: + /* During commit phase, just save any new authoritative commit */ + sr_state_add_commit(commit); + break; + case SR_PHASE_REVEAL: + save_commit_during_reveal_phase(commit); + sr_commit_free(commit); + break; + default: + tor_assert(0); + } +} + +/* Return the number of required participants of the SR protocol. This is based + * on a consensus params. */ static int get_n_voters_for_srv_agreement(void) { @@ -789,6 +1103,41 @@ sr_parse_commit(const smartlist_t *args) return NULL; } +/* Called when we are done parsing a vote by voter_key that might + * contain some useful commits. Find if any of them should be kept + * and update our state accordingly. Once done, the list of commitments will + * be empty. */ +void +sr_handle_received_commits(smartlist_t *commits, crypto_pk_t *voter_key) +{ + char rsa_identity_fpr[FINGERPRINT_LEN + 1]; + + tor_assert(voter_key); + + /* It's possible that the vote has _NO_ commits. */ + if (commits == NULL) { + return; + } + + /* Get the RSA identity fingerprint of this voter */ + if (crypto_pk_get_fingerprint(voter_key, rsa_identity_fpr, 0) < 0) { + return; + } + + SMARTLIST_FOREACH_BEGIN(commits, sr_commit_t *, commit) { + /* We won't need the commit in this list anymore, kept or not. */ + SMARTLIST_DEL_CURRENT(commits, commit); + /* Check if this commit is valid and should be stored in our state. */ + if (!should_keep_commit(commit, rsa_identity_fpr, + sr_state_get_phase())) { + sr_commit_free(commit); + continue; + } + /* Everything lines up: save this commit to state then! */ + save_commit_to_state(commit); + } SMARTLIST_FOREACH_END(commit); +} + /* Return a heap-allocated string containing commits that should be put in * the votes. It's the responsibility of the caller to free the string. * This always return a valid string, either empty or with line(s). */ @@ -873,6 +1222,48 @@ sr_get_string_for_consensus(const smartlist_t *votes) return NULL; } +/* We just computed a new consensus. Update our state with the SRVs + * from the consensus (might be NULL as well). Register the SRVs in our SR + * state and prepare for the upcoming protocol round. */ +void +sr_act_post_consensus(const networkstatus_t *consensus) +{ + time_t interval_starts; + const or_options_t *options = get_options(); + + /* Don't act if our state hasn't been initialized. We can be called during + * boot time when loading consensus from disk which is prior to the + * initialization of the SR subsystem. We also should not be doing + * anything if we are _not_ a directory authority and if we are a bridge + * authority. */ + if (!sr_state_is_initialized() || !authdir_mode_v3(options) || + authdir_mode_bridge(options)) { + return; + } + + /* Set the majority voted SRVs in our state even if both are NULL. It + * doesn't matter this is what the majority has decided. Obviously, we can + * only do that if we have a consensus. */ + if (consensus) { + /* Start by freeing the current SRVs since the SRVs we believed during + * voting do not really matter. Now that all the votes are in, we use the + * majority's opinion on which are the active SRVs. */ + sr_state_clean_srvs(); + /* Reset the fresh flag of the SRV so we know that from now on we don't + * have a new SRV to vote for. We just used the one from the consensus + * decided by the majority. */ + sr_state_unset_fresh_srv(); + /* Set the SR values from the given consensus. */ + sr_state_set_previous_srv(srv_dup(consensus->sr_info.previous_srv)); + sr_state_set_current_srv(srv_dup(consensus->sr_info.current_srv)); + } + + /* Update our internal state with the next voting interval starting time. */ + interval_starts = get_voting_schedule(options, time(NULL), + LOG_NOTICE)->interval_starts; + sr_state_update(interval_starts); +} + /* Initialize shared random subsystem. This MUST be called early in the boot * process of tor. Return 0 on success else -1 on error. */ int diff --git a/src/or/shared_random.h b/src/or/shared_random.h index f1fbe273f1..d52258c6e2 100644 --- a/src/or/shared_random.h +++ b/src/or/shared_random.h @@ -101,6 +101,9 @@ typedef struct sr_commit_t { int sr_init(int save_to_disk); void sr_save_and_cleanup(void); +void sr_act_post_consensus(const networkstatus_t *consensus); +void sr_handle_received_commits(smartlist_t *commits, + crypto_pk_t *voter_key); sr_commit_t *sr_parse_commit(const smartlist_t *args); sr_srv_t *sr_parse_srv(const smartlist_t *args); char *sr_get_string_for_vote(void); @@ -125,9 +128,22 @@ STATIC int reveal_decode(const char *encoded, sr_commit_t *commit); STATIC int commit_has_reveal_value(const sr_commit_t *commit); +STATIC int verify_commit_and_reveal(const sr_commit_t *commit); + STATIC sr_srv_t *get_majority_srv_from_votes(const smartlist_t *votes, int current); +STATIC void save_commit_to_state(sr_commit_t *commit); +STATIC sr_srv_t *srv_dup(const sr_srv_t *orig); +STATIC int commitments_are_the_same(const sr_commit_t *commit_one, + const sr_commit_t *commit_two); +STATIC int commit_is_authoritative(const sr_commit_t *commit, + const char *voter_key); +STATIC int should_keep_commit(const sr_commit_t *commit, + const char *voter_key, + sr_phase_t phase); +STATIC void save_commit_during_reveal_phase(const sr_commit_t *commit); + #endif /* SHARED_RANDOM_PRIVATE */ #endif /* TOR_SHARED_RANDOM_H */ diff --git a/src/or/shared_random_state.c b/src/or/shared_random_state.c index 87bdfc048f..6dd10d612e 100644 --- a/src/or/shared_random_state.c +++ b/src/or/shared_random_state.c @@ -1160,6 +1160,27 @@ sr_state_delete_commits(void) state_query(SR_STATE_ACTION_DEL_ALL, SR_STATE_OBJ_COMMIT, NULL, NULL); } +/* Copy the reveal information from commit into saved_commit. + * This saved_commit MUST come from our current SR state. Once modified, + * the disk state is updated. */ +void +sr_state_copy_reveal_info(sr_commit_t *saved_commit, const sr_commit_t *commit) +{ + tor_assert(saved_commit); + tor_assert(commit); + + saved_commit->reveal_ts = commit->reveal_ts; + memcpy(saved_commit->random_number, commit->random_number, + sizeof(saved_commit->random_number)); + + strlcpy(saved_commit->encoded_reveal, commit->encoded_reveal, + sizeof(saved_commit->encoded_reveal)); + state_query(SR_STATE_ACTION_SAVE, 0, NULL, NULL); + log_debug(LD_DIR, "SR: Reveal value learned %s (for commit %s) from %s", + saved_commit->encoded_reveal, saved_commit->encoded_commit, + saved_commit->rsa_identity_fpr); +} + /* Set the fresh SRV flag from our state. This doesn't need to trigger a * disk state synchronization so we directly change the state. */ void @@ -1202,6 +1223,14 @@ sr_state_save(void) state_query(SR_STATE_ACTION_SAVE, 0, NULL, NULL); } +/* Return 1 iff the state has been initialized that is it exists in memory. + * Return 0 otherwise. */ +int +sr_state_is_initialized(void) +{ + return sr_state == NULL ? 0 : 1; +} + /* Initialize the disk and memory state. * * If save_to_disk is set to 1, the state is immediately saved to disk after diff --git a/src/or/shared_random_state.h b/src/or/shared_random_state.h index d9d97510da..499a3754b6 100644 --- a/src/or/shared_random_state.h +++ b/src/or/shared_random_state.h @@ -116,6 +116,7 @@ unsigned int sr_state_srv_is_fresh(void); void sr_state_set_fresh_srv(void); void sr_state_unset_fresh_srv(void); int sr_state_init(int save_to_disk, int read_from_disk); +int sr_state_is_initialized(void); void sr_state_save(void); void sr_state_free(void); From 727d419a9d0845e7820364e8a8f0928043c67111 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 3 May 2016 11:44:26 -0400 Subject: [PATCH 06/26] prop250: Initialize the SR subsystem and us it! This commit makes it that tor now uses the shared random protocol by initializing the subsystem. Signed-off-by: David Goulet Signed-off-by: George Kadianakis --- src/or/main.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/or/main.c b/src/or/main.c index 9f3306dc4f..d7cd701a0f 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -57,6 +57,7 @@ #include "routerlist.h" #include "routerparse.h" #include "scheduler.h" +#include "shared_random.h" #include "statefile.h" #include "status.h" #include "util_process.h" @@ -2441,6 +2442,13 @@ do_main_loop(void) cpu_init(); } + /* Setup shared random protocol subsystem. */ + if (authdir_mode_publishes_statuses(get_options())) { + if (sr_init(1) < 0) { + return -1; + } + } + /* set up once-a-second callback. */ if (! second_timer) { struct timeval one_second; @@ -3204,6 +3212,9 @@ tor_cleanup(void) accounting_record_bandwidth_usage(now, get_or_state()); or_state_mark_dirty(get_or_state(), 0); /* force an immediate save. */ or_state_save(now); + if (authdir_mode(options)) { + sr_save_and_cleanup(); + } if (authdir_mode_tests_reachability(options)) rep_hist_record_mtbf_data(now, 0); keypin_close_journal(); @@ -3362,6 +3373,7 @@ sandbox_init_filter(void) OPEN_DATADIR_SUFFIX("cached-extrainfo.new", ".tmp"); OPEN_DATADIR("cached-extrainfo.tmp.tmp"); OPEN_DATADIR_SUFFIX("state", ".tmp"); + OPEN_DATADIR_SUFFIX("sr-state", ".tmp"); OPEN_DATADIR_SUFFIX("unparseable-desc", ".tmp"); OPEN_DATADIR_SUFFIX("v3-status-votes", ".tmp"); OPEN_DATADIR("key-pinning-journal"); @@ -3414,6 +3426,7 @@ sandbox_init_filter(void) RENAME_SUFFIX("cached-extrainfo", ".new"); RENAME_SUFFIX("cached-extrainfo.new", ".tmp"); RENAME_SUFFIX("state", ".tmp"); + RENAME_SUFFIX("sr-state", ".tmp"); RENAME_SUFFIX("unparseable-desc", ".tmp"); RENAME_SUFFIX("v3-status-votes", ".tmp"); From 39be8af7092a4ee004f4c53fa1e55678d9d67f1f Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 3 May 2016 11:42:50 -0400 Subject: [PATCH 07/26] prop250: Add unit tests Signed-off-by: David Goulet Signed-off-by: George Kadianakis --- src/or/networkstatus.c | 8 +- src/or/networkstatus.h | 4 +- src/or/shared_random_state.c | 19 + src/or/shared_random_state.h | 7 + src/test/include.am | 1 + src/test/sr_srv_calc_ref.py | 71 ++ src/test/test.c | 1 + src/test/test.h | 1 + src/test/test_dir.c | 73 +- src/test/test_routerlist.c | 17 + src/test/test_shared_random.c | 1256 +++++++++++++++++++++++++++++++++ 11 files changed, 1449 insertions(+), 9 deletions(-) create mode 100644 src/test/sr_srv_calc_ref.py create mode 100644 src/test/test_shared_random.c diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index aabbffff1d..cf395f9c99 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -1191,8 +1191,8 @@ consensus_is_waiting_for_certs(void) /** Return the most recent consensus that we have downloaded, or NULL if we * don't have one. */ -networkstatus_t * -networkstatus_get_latest_consensus(void) +MOCK_IMPL(networkstatus_t *, +networkstatus_get_latest_consensus,(void)) { return current_consensus; } @@ -1214,8 +1214,8 @@ networkstatus_get_latest_consensus_by_flavor,(consensus_flavor_t f)) /** Return the most recent consensus that we have downloaded, or NULL if it is * no longer live. */ -networkstatus_t * -networkstatus_get_live_consensus(time_t now) +MOCK_IMPL(networkstatus_t *, +networkstatus_get_live_consensus,(time_t now)) { if (current_consensus && current_consensus->valid_after <= now && diff --git a/src/or/networkstatus.h b/src/or/networkstatus.h index aee6641c6e..b34696e5ae 100644 --- a/src/or/networkstatus.h +++ b/src/or/networkstatus.h @@ -64,10 +64,10 @@ void update_certificate_downloads(time_t now); int consensus_is_waiting_for_certs(void); int client_would_use_router(const routerstatus_t *rs, time_t now, const or_options_t *options); -networkstatus_t *networkstatus_get_latest_consensus(void); +MOCK_DECL(networkstatus_t *,networkstatus_get_latest_consensus,(void)); MOCK_DECL(networkstatus_t *,networkstatus_get_latest_consensus_by_flavor, (consensus_flavor_t f)); -networkstatus_t *networkstatus_get_live_consensus(time_t now); +MOCK_DECL(networkstatus_t *, networkstatus_get_live_consensus,(time_t now)); networkstatus_t *networkstatus_get_reasonably_live_consensus(time_t now, int flavor); MOCK_DECL(int, networkstatus_consensus_is_bootstrapping,(time_t now)); diff --git a/src/or/shared_random_state.c b/src/or/shared_random_state.c index 6dd10d612e..7c75431040 100644 --- a/src/or/shared_random_state.c +++ b/src/or/shared_random_state.c @@ -1291,3 +1291,22 @@ sr_state_init(int save_to_disk, int read_from_disk) error: return -1; } + +#ifdef TOR_UNIT_TESTS + +/* Set the current phase of the protocol. Used only by unit tests. */ +void +set_sr_phase(sr_phase_t phase) +{ + tor_assert(sr_state); + sr_state->phase = phase; +} + +/* Get the SR state. Used only by unit tests */ +sr_state_t * +get_sr_state(void) +{ + return sr_state; +} + +#endif /* TOR_UNIT_TESTS */ diff --git a/src/or/shared_random_state.h b/src/or/shared_random_state.h index 499a3754b6..d013650aa2 100644 --- a/src/or/shared_random_state.h +++ b/src/or/shared_random_state.h @@ -135,4 +135,11 @@ STATIC int is_phase_transition(sr_phase_t next_phase); #endif /* SHARED_RANDOM_STATE_PRIVATE */ +#ifdef TOR_UNIT_TESTS + +STATIC void set_sr_phase(sr_phase_t phase); +STATIC sr_state_t *get_sr_state(void); + +#endif /* TOR_UNIT_TESTS */ + #endif /* TOR_SHARED_RANDOM_STATE_H */ diff --git a/src/test/include.am b/src/test/include.am index 5a91c74cde..d0bc808877 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -116,6 +116,7 @@ src_test_test_SOURCES = \ src/test/test_routerlist.c \ src/test/test_routerset.c \ src/test/test_scheduler.c \ + src/test/test_shared_random.c \ src/test/test_socks.c \ src/test/test_status.c \ src/test/test_threads.c \ diff --git a/src/test/sr_srv_calc_ref.py b/src/test/sr_srv_calc_ref.py new file mode 100644 index 0000000000..492ca62b15 --- /dev/null +++ b/src/test/sr_srv_calc_ref.py @@ -0,0 +1,71 @@ +# This is a reference implementation of the SRV calculation for prop250. We +# use it to generate a test vector for the test_sr_compute_srv() unittest. +# (./test shared-random/sr_compute_srv) +# +# Here is the SRV computation formula: +# +# HASHED_REVEALS = H(ID_a | R_a | ID_b | R_b | ..) +# +# SRV = SHA3-256("shared-random" | INT_8(reveal_num) | INT_4(version) | +# HASHED_REVEALS | previous_SRV) +# + +import sys +import hashlib +import struct + +# Python 3.6+, the SHA3 is available in hashlib natively. Else this requires +# the pysha3 package (pip install pysha3). +if sys.version_info < (3, 6): + import sha3 + +# Test vector to make sure the right sha3 version will be used. pysha3 < 1.0 +# used the old Keccak implementation. During the finalization of SHA3, NIST +# changed the delimiter suffix from 0x01 to 0x06. The Keccak sponge function +# stayed the same. pysha3 1.0 provides the previous Keccak hash, too. +TEST_VALUE = "e167f68d6563d75bb25f3aa49c29ef612d41352dc00606de7cbd630bb2665f51" +if TEST_VALUE != sha3.sha3_256(b"Hello World").hexdigest(): + print("pysha3 version is < 1.0. Please install from:") + print("https://github.com/tiran/pysha3https://github.com/tiran/pysha3") + sys.exit(1) + +# In this example, we use three reveal values. +reveal_num = 3 +version = 1 + +# We set directly the ascii value because memset(buf, 'A', 20) makes it to 20 +# times "41" in the final string. + +# Identity and reveal value of dirauth a +ID_a = 20 * "41" # RSA identity of 40 base16 bytes. +R_a = 56 * 'A' # 56 base64 characters + +# Identity and reveal value of dirauth b +ID_b = 20 * "42" # RSA identity of 40 base16 bytes. +R_b = 56 * 'B' # 56 base64 characters + +# Identity and reveal value of dirauth c +ID_c = 20 * "43" # RSA identity of 40 base16 bytes. +R_c = 56 * 'C' # 56 base64 characters + +# Concatenate them all together and hash them to form HASHED_REVEALS. +REVEALS = (ID_a + R_a + ID_b + R_b + ID_c + R_c).encode() +hashed_reveals_object = hashlib.sha3_256(REVEALS) +hashed_reveals = hashed_reveals_object.digest() + +previous_SRV = (32 * 'Z').encode() + +# Now form the message. +#srv_msg = struct.pack('13sQL256ss', "shared-random", reveal_num, version, +# hashed_reveals, previous_SRV) +invariant_token = b"shared-random" +srv_msg = invariant_token + \ + struct.pack('!QL', reveal_num, version) + \ + hashed_reveals + \ + previous_SRV + +# Now calculate the HMAC +srv = hashlib.sha3_256(srv_msg) +print("%s" % srv.hexdigest().upper()) + +# 2A9B1D6237DAB312A40F575DA85C147663E7ED3F80E9555395F15B515C74253D diff --git a/src/test/test.c b/src/test/test.c index c0faec3027..3a1054decf 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -1170,6 +1170,7 @@ struct testgroup_t testgroups[] = { { "routerset/" , routerset_tests }, { "scheduler/", scheduler_tests }, { "socks/", socks_tests }, + { "shared-random/", sr_tests }, { "status/" , status_tests }, { "tortls/", tortls_tests }, { "util/", util_tests }, diff --git a/src/test/test.h b/src/test/test.h index 747b61d669..6744d255f1 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -225,6 +225,7 @@ extern struct testcase_t util_format_tests[]; extern struct testcase_t util_process_tests[]; extern struct testcase_t dns_tests[]; extern struct testcase_t handle_tests[]; +extern struct testcase_t sr_tests[]; extern struct testcase_t slow_crypto_tests[]; extern struct testcase_t slow_util_tests[]; diff --git a/src/test/test_dir.c b/src/test/test_dir.c index b7d58bd577..b0ae2d99c8 100644 --- a/src/test/test_dir.c +++ b/src/test/test_dir.c @@ -30,6 +30,7 @@ #include "routerlist.h" #include "routerparse.h" #include "routerset.h" +#include "shared_random_state.h" #include "test.h" #include "test_dir_common.h" #include "torcert.h" @@ -1544,6 +1545,43 @@ test_dir_param_voting(void *arg) return; } +static void +test_dir_param_voting_lookup(void *arg) +{ + (void)arg; + smartlist_t *lst = smartlist_new(); + + smartlist_split_string(lst, + "moomin=9 moomin=10 moomintroll=5 fred " + "jack= electricity=sdk opa=6z abc=9 abcd=99", + NULL, 0, 0); + + tt_int_op(1000, + OP_EQ, dirvote_get_intermediate_param_value(lst, "ab", 1000)); + tt_int_op(9, OP_EQ, dirvote_get_intermediate_param_value(lst, "abc", 1000)); + tt_int_op(99, OP_EQ, dirvote_get_intermediate_param_value(lst, "abcd", 1000)); + + /* moomin appears twice. */ + tt_int_op(-100, OP_EQ, + dirvote_get_intermediate_param_value(lst, "moomin", -100)); + /* fred and jack are truncated */ + tt_int_op(-100, OP_EQ, + dirvote_get_intermediate_param_value(lst, "fred", -100)); + tt_int_op(-100, OP_EQ, + dirvote_get_intermediate_param_value(lst, "jack", -100)); + /* electricity and opa aren't integers. */ + tt_int_op(-100, OP_EQ, + dirvote_get_intermediate_param_value(lst, "electricity", -100)); + tt_int_op(-100, OP_EQ, + dirvote_get_intermediate_param_value(lst, "opa", -100)); + + done: + SMARTLIST_FOREACH(lst, char *, cp, tor_free(cp)); + smartlist_free(lst); +} + +#undef dirvote_compute_params + /** Helper: Test that two networkstatus_voter_info_t do in fact represent the * same voting authority, and that they do in fact have all the same * information. */ @@ -1788,6 +1826,15 @@ test_routerstatus_for_v3ns(routerstatus_t *rs, time_t now) return; } +static authority_cert_t *mock_cert; + +static authority_cert_t * +get_my_v3_authority_cert_m(void) +{ + tor_assert(mock_cert); + return mock_cert; +} + /** Run a unit tests for generating and parsing networkstatuses, with * the supply test fns. */ static void @@ -1831,10 +1878,30 @@ test_a_networkstatus( tt_assert(rs_test); tt_assert(vrs_test); - tt_assert(!dir_common_authority_pk_init(&cert1, &cert2, &cert3, - &sign_skey_1, &sign_skey_2, - &sign_skey_3)); + MOCK(get_my_v3_authority_cert, get_my_v3_authority_cert_m); + + /* Parse certificates and keys. */ + cert1 = mock_cert = authority_cert_parse_from_string(AUTHORITY_CERT_1, NULL); + tt_assert(cert1); + cert2 = authority_cert_parse_from_string(AUTHORITY_CERT_2, NULL); + tt_assert(cert2); + cert3 = authority_cert_parse_from_string(AUTHORITY_CERT_3, NULL); + tt_assert(cert3); + sign_skey_1 = crypto_pk_new(); + sign_skey_2 = crypto_pk_new(); + sign_skey_3 = crypto_pk_new(); sign_skey_leg1 = pk_generate(4); + sr_state_init(0, 0); + + tt_assert(!crypto_pk_read_private_key_from_string(sign_skey_1, + AUTHORITY_SIGNKEY_1, -1)); + tt_assert(!crypto_pk_read_private_key_from_string(sign_skey_2, + AUTHORITY_SIGNKEY_2, -1)); + tt_assert(!crypto_pk_read_private_key_from_string(sign_skey_3, + AUTHORITY_SIGNKEY_3, -1)); + + tt_assert(!crypto_pk_cmp_keys(sign_skey_1, cert1->signing_key)); + tt_assert(!crypto_pk_cmp_keys(sign_skey_2, cert2->signing_key)); tt_assert(!dir_common_construct_vote_1(&vote, cert1, sign_skey_1, vrs_gen, &v1, &n_vrs, now, 1)); diff --git a/src/test/test_routerlist.c b/src/test/test_routerlist.c index 34b70ac8a8..088bd257c3 100644 --- a/src/test/test_routerlist.c +++ b/src/test/test_routerlist.c @@ -19,13 +19,24 @@ #include "networkstatus.h" #include "nodelist.h" #include "policies.h" +#include "router.h" #include "routerlist.h" #include "routerparse.h" +#include "shared_random.h" #include "test.h" #include "test_dir_common.h" void construct_consensus(char **consensus_text_md); +static authority_cert_t *mock_cert; + +static authority_cert_t * +get_my_v3_authority_cert_m(void) +{ + tor_assert(mock_cert); + return mock_cert; +} + /* 4 digests + 3 sep + pre + post + NULL */ static char output[4*BASE64_DIGEST256_LEN+3+2+2+1]; @@ -227,6 +238,12 @@ test_router_pick_directory_server_impl(void *arg) tt_assert(networkstatus_consensus_is_bootstrapping(now + 2*24*60*60)); tt_assert(networkstatus_consensus_is_bootstrapping(now - 2*24*60*60)); + /* Init SR subsystem. */ + MOCK(get_my_v3_authority_cert, get_my_v3_authority_cert_m); + mock_cert = authority_cert_parse_from_string(AUTHORITY_CERT_1, NULL); + sr_init(0); + UNMOCK(get_my_v3_authority_cert); + /* No consensus available, fail early */ rs = router_pick_directory_server_impl(V3_DIRINFO, (const int) 0, NULL); tt_assert(rs == NULL); diff --git a/src/test/test_shared_random.c b/src/test/test_shared_random.c new file mode 100644 index 0000000000..112571784f --- /dev/null +++ b/src/test/test_shared_random.c @@ -0,0 +1,1256 @@ +#define SHARED_RANDOM_PRIVATE +#define SHARED_RANDOM_STATE_PRIVATE +#define CONFIG_PRIVATE +#define DIRVOTE_PRIVATE + +#include "or.h" +#include "test.h" +#include "config.h" +#include "dirvote.h" +#include "shared_random.h" +#include "shared_random_state.h" +#include "routerkeys.h" +#include "routerlist.h" +#include "router.h" +#include "routerparse.h" +#include "networkstatus.h" + +static authority_cert_t *mock_cert; + +static authority_cert_t * +get_my_v3_authority_cert_m(void) +{ + tor_assert(mock_cert); + return mock_cert; +} + +/* Setup a minimal dirauth environment by initializing the SR state and + * making sure the options are set to be an authority directory. */ +static void +init_authority_state(void) +{ + MOCK(get_my_v3_authority_cert, get_my_v3_authority_cert_m); + + or_options_t *options = get_options_mutable(); + mock_cert = authority_cert_parse_from_string(AUTHORITY_CERT_1, NULL); + tt_assert(mock_cert); + options->AuthoritativeDir = 1; + tt_int_op(0, ==, load_ed_keys(options, time(NULL))); + sr_state_init(0, 0); + /* It's possible a commit has been generated in our state depending on + * the phase we are currently in which uses "now" as the starting + * timestamp. Delete it before we do any testing below. */ + sr_state_delete_commits(); + + done: + UNMOCK(get_my_v3_authority_cert); +} + +static void +test_get_sr_protocol_phase(void *arg) +{ + time_t the_time; + sr_phase_t phase; + int retval; + + (void) arg; + + /* Initialize SR state */ + init_authority_state(); + + { + retval = parse_rfc1123_time("Wed, 20 Apr 2015 23:59:00 UTC", &the_time); + tt_int_op(retval, ==, 0); + + phase = get_sr_protocol_phase(the_time); + tt_int_op(phase, ==, SR_PHASE_REVEAL); + } + + { + retval = parse_rfc1123_time("Wed, 20 Apr 2015 00:00:00 UTC", &the_time); + tt_int_op(retval, ==, 0); + + phase = get_sr_protocol_phase(the_time); + tt_int_op(phase, ==, SR_PHASE_COMMIT); + } + + { + retval = parse_rfc1123_time("Wed, 20 Apr 2015 00:00:01 UTC", &the_time); + tt_int_op(retval, ==, 0); + + phase = get_sr_protocol_phase(the_time); + tt_int_op(phase, ==, SR_PHASE_COMMIT); + } + + { + retval = parse_rfc1123_time("Wed, 20 Apr 2015 11:59:00 UTC", &the_time); + tt_int_op(retval, ==, 0); + + phase = get_sr_protocol_phase(the_time); + tt_int_op(phase, ==, SR_PHASE_COMMIT); + } + + { + retval = parse_rfc1123_time("Wed, 20 Apr 2015 12:00:00 UTC", &the_time); + tt_int_op(retval, ==, 0); + + phase = get_sr_protocol_phase(the_time); + tt_int_op(phase, ==, SR_PHASE_REVEAL); + } + + { + retval = parse_rfc1123_time("Wed, 20 Apr 2015 12:00:01 UTC", &the_time); + tt_int_op(retval, ==, 0); + + phase = get_sr_protocol_phase(the_time); + tt_int_op(phase, ==, SR_PHASE_REVEAL); + } + + { + retval = parse_rfc1123_time("Wed, 20 Apr 2015 13:00:00 UTC", &the_time); + tt_int_op(retval, ==, 0); + + phase = get_sr_protocol_phase(the_time); + tt_int_op(phase, ==, SR_PHASE_REVEAL); + } + + done: + ; +} + +static networkstatus_t *mock_consensus = NULL; + +static void +test_get_state_valid_until_time(void *arg) +{ + time_t current_time; + time_t valid_until_time; + char tbuf[ISO_TIME_LEN + 1]; + int retval; + + (void) arg; + + { + /* Get the valid until time if called at 00:00:01 */ + retval = parse_rfc1123_time("Mon, 20 Apr 2015 00:00:01 UTC", + ¤t_time); + tt_int_op(retval, ==, 0); + valid_until_time = get_state_valid_until_time(current_time); + + /* Compare it with the correct result */ + format_iso_time(tbuf, valid_until_time); + tt_str_op("2015-04-21 00:00:00", OP_EQ, tbuf); + } + + { + retval = parse_rfc1123_time("Mon, 20 Apr 2015 19:22:00 UTC", + ¤t_time); + tt_int_op(retval, ==, 0); + valid_until_time = get_state_valid_until_time(current_time); + + format_iso_time(tbuf, valid_until_time); + tt_str_op("2015-04-21 00:00:00", OP_EQ, tbuf); + } + + { + retval = parse_rfc1123_time("Mon, 20 Apr 2015 23:59:00 UTC", + ¤t_time); + tt_int_op(retval, ==, 0); + valid_until_time = get_state_valid_until_time(current_time); + + format_iso_time(tbuf, valid_until_time); + tt_str_op("2015-04-21 00:00:00", OP_EQ, tbuf); + } + + { + retval = parse_rfc1123_time("Mon, 20 Apr 2015 00:00:00 UTC", + ¤t_time); + tt_int_op(retval, ==, 0); + valid_until_time = get_state_valid_until_time(current_time); + + format_iso_time(tbuf, valid_until_time); + tt_str_op("2015-04-21 00:00:00", OP_EQ, tbuf); + } + + done: + ; +} + +/* Mock function to immediately return our local 'mock_consensus'. */ +static networkstatus_t * +mock_networkstatus_get_live_consensus(time_t now) +{ + (void) now; + return mock_consensus; +} + +/** Test the get_next_valid_after_time() function. */ +static void +test_get_next_valid_after_time(void *arg) +{ + time_t current_time; + time_t valid_after_time; + char tbuf[ISO_TIME_LEN + 1]; + int retval; + + (void) arg; + + { + /* Setup a fake consensus just to get the times out of it, since + get_next_valid_after_time() needs them. */ + mock_consensus = tor_malloc_zero(sizeof(networkstatus_t)); + + retval = parse_rfc1123_time("Mon, 13 Jan 2016 16:00:00 UTC", + &mock_consensus->fresh_until); + tt_int_op(retval, ==, 0); + + retval = parse_rfc1123_time("Mon, 13 Jan 2016 15:00:00 UTC", + &mock_consensus->valid_after); + tt_int_op(retval, ==, 0); + + MOCK(networkstatus_get_live_consensus, + mock_networkstatus_get_live_consensus); + } + + { + /* Get the valid after time if called at 00:00:00 */ + retval = parse_rfc1123_time("Mon, 20 Apr 2015 00:00:00 UTC", + ¤t_time); + tt_int_op(retval, ==, 0); + valid_after_time = get_next_valid_after_time(current_time); + + /* Compare it with the correct result */ + format_iso_time(tbuf, valid_after_time); + tt_str_op("2015-04-20 01:00:00", OP_EQ, tbuf); + } + + { + /* Get the valid until time if called at 00:00:01 */ + retval = parse_rfc1123_time("Mon, 20 Apr 2015 00:00:01 UTC", + ¤t_time); + tt_int_op(retval, ==, 0); + valid_after_time = get_next_valid_after_time(current_time); + + /* Compare it with the correct result */ + format_iso_time(tbuf, valid_after_time); + tt_str_op("2015-04-20 01:00:00", OP_EQ, tbuf); + } + + { + retval = parse_rfc1123_time("Mon, 20 Apr 2015 23:30:01 UTC", + ¤t_time); + tt_int_op(retval, ==, 0); + valid_after_time = get_next_valid_after_time(current_time); + + /* Compare it with the correct result */ + format_iso_time(tbuf, valid_after_time); + tt_str_op("2015-04-21 00:00:00", OP_EQ, tbuf); + } + + done: + networkstatus_vote_free(mock_consensus); +} + +/* In this test we are going to generate a sr_commit_t object and validate + * it. We first generate our values, and then we parse them as if they were + * received from the network. After we parse both the commit and the reveal, + * we verify that they indeed match. */ +static void +test_sr_commit(void *arg) +{ + authority_cert_t *auth_cert = NULL; + time_t now = time(NULL); + sr_commit_t *our_commit = NULL; + smartlist_t *args = smartlist_new(); + + (void) arg; + + { /* Setup a minimal dirauth environment for this test */ + or_options_t *options = get_options_mutable(); + + auth_cert = authority_cert_parse_from_string(AUTHORITY_CERT_1, NULL); + tt_assert(auth_cert); + + options->AuthoritativeDir = 1; + tt_int_op(0, ==, load_ed_keys(options, now)); + } + + /* Generate our commit object and validate it has the appropriate field + * that we can then use to build a representation that we'll find in a + * vote coming from the network. */ + { + sr_commit_t test_commit; + our_commit = sr_generate_our_commit(now, auth_cert); + tt_assert(our_commit); + /* Default and only supported algorithm for now. */ + tt_assert(our_commit->alg == DIGEST_SHA3_256); + /* We should have a reveal value. */ + tt_assert(commit_has_reveal_value(our_commit)); + /* We should have a random value. */ + tt_assert(!tor_mem_is_zero((char *) our_commit->random_number, + sizeof(our_commit->random_number))); + /* Commit and reveal timestamp should be the same. */ + tt_int_op(our_commit->commit_ts, ==, our_commit->reveal_ts); + /* We should have a hashed reveal. */ + tt_assert(!tor_mem_is_zero(our_commit->hashed_reveal, + sizeof(our_commit->hashed_reveal))); + /* Do we have a valid encoded commit and reveal. Note the following only + * tests if the generated values are correct. Their could be a bug in + * the decode function but we test them seperately. */ + tt_int_op(0, ==, reveal_decode(our_commit->encoded_reveal, + &test_commit)); + tt_int_op(0, ==, commit_decode(our_commit->encoded_commit, + &test_commit)); + tt_int_op(0, ==, verify_commit_and_reveal(our_commit)); + } + + /* Let's make sure our verify commit and reveal function works. We'll + * make it fail a bit with known failure case. */ + { + /* Copy our commit so we don't alter it for the rest of testing. */ + sr_commit_t test_commit; + memcpy(&test_commit, our_commit, sizeof(test_commit)); + + /* Timestamp MUST match. */ + test_commit.commit_ts = test_commit.reveal_ts - 42; + tt_int_op(-1, ==, verify_commit_and_reveal(&test_commit)); + memcpy(&test_commit, our_commit, sizeof(test_commit)); + tt_int_op(0, ==, verify_commit_and_reveal(&test_commit)); + + /* Hashed reveal must match the H(encoded_reveal). */ + memset(test_commit.hashed_reveal, 'X', + sizeof(test_commit.hashed_reveal)); + tt_int_op(-1, ==, verify_commit_and_reveal(&test_commit)); + memcpy(&test_commit, our_commit, sizeof(test_commit)); + tt_int_op(0, ==, verify_commit_and_reveal(&test_commit)); + } + + /* We'll build a list of values from our commit that our parsing function + * takes from a vote line and see if we can parse it correctly. */ + { + sr_commit_t *parsed_commit; + smartlist_add(args, + tor_strdup(crypto_digest_algorithm_get_name(our_commit->alg))); + smartlist_add(args, our_commit->rsa_identity_fpr); + smartlist_add(args, our_commit->encoded_commit); + smartlist_add(args, our_commit->encoded_reveal); + parsed_commit = sr_parse_commit(args); + tt_assert(parsed_commit); + /* That parsed commit should be _EXACTLY_ like our original commit. */ + tt_mem_op(parsed_commit, OP_EQ, our_commit, sizeof(*parsed_commit)); + /* Cleanup */ + tor_free(smartlist_get(args, 0)); /* strdup here. */ + smartlist_clear(args); + sr_commit_free(parsed_commit); + } + + done: + smartlist_free(args); + sr_commit_free(our_commit); +} + +/* Test the encoding and decoding function for commit and reveal values. */ +static void +test_encoding(void *arg) +{ + (void) arg; + int ret, duper_rand = 42; + /* Random number is 32 bytes. */ + char raw_rand[32]; + time_t ts = 1454333590; + char hashed_rand[DIGEST256_LEN], hashed_reveal[DIGEST256_LEN]; + sr_commit_t parsed_commit; + + /* Encoded commit is: base64-encode( 1454333590 || H(H(42)) ). Remember + * that we do no expose the raw bytes of our PRNG to the network thus + * explaining the double H(). */ + static const char *encoded_commit = + "AAAAAFavXpZbx2LRneYFSLPCP8DLp9BXfeH5FXzbkxM4iRXKGeA54g=="; + /* Encoded reveal is: base64-encode( 1454333590 || H(42) ). */ + static const char *encoded_reveal = + "AAAAAFavXpYk9x9kTjiQWUqjHwSAEOdPAfCaurXgjPy173SzYjeC2g=="; + + /* Set up our raw random bytes array. */ + memset(raw_rand, 0, sizeof(raw_rand)); + memcpy(raw_rand, &duper_rand, sizeof(duper_rand)); + /* Hash random number. */ + ret = crypto_digest256(hashed_rand, raw_rand, + sizeof(raw_rand), SR_DIGEST_ALG); + tt_int_op(0, ==, ret); + /* Hash reveal value. */ + tt_int_op(SR_REVEAL_BASE64_LEN, ==, strlen(encoded_reveal)); + ret = crypto_digest256(hashed_reveal, encoded_reveal, + strlen(encoded_reveal), SR_DIGEST_ALG); + tt_int_op(0, ==, ret); + tt_int_op(SR_COMMIT_BASE64_LEN, ==, strlen(encoded_commit)); + + /* Test our commit/reveal decode functions. */ + { + /* Test the reveal encoded value. */ + tt_int_op(0, ==, reveal_decode(encoded_reveal, &parsed_commit)); + tt_uint_op(ts, ==, parsed_commit.reveal_ts); + tt_mem_op(hashed_rand, OP_EQ, parsed_commit.random_number, + sizeof(hashed_rand)); + + /* Test the commit encoded value. */ + memset(&parsed_commit, 0, sizeof(parsed_commit)); + tt_int_op(0, ==, commit_decode(encoded_commit, &parsed_commit)); + tt_uint_op(ts, ==, parsed_commit.commit_ts); + tt_mem_op(encoded_commit, OP_EQ, parsed_commit.encoded_commit, + sizeof(parsed_commit.encoded_commit)); + tt_mem_op(hashed_reveal, OP_EQ, parsed_commit.hashed_reveal, + sizeof(hashed_reveal)); + } + + /* Test our commit/reveal encode functions. */ + { + /* Test the reveal encode. */ + char encoded[SR_REVEAL_BASE64_LEN + 1]; + parsed_commit.reveal_ts = ts; + memcpy(parsed_commit.random_number, hashed_rand, + sizeof(parsed_commit.random_number)); + ret = reveal_encode(&parsed_commit, encoded, sizeof(encoded)); + tt_int_op(SR_REVEAL_BASE64_LEN, ==, ret); + tt_mem_op(encoded_reveal, OP_EQ, encoded, strlen(encoded_reveal)); + } + + { + /* Test the commit encode. */ + char encoded[SR_COMMIT_BASE64_LEN + 1]; + parsed_commit.commit_ts = ts; + memcpy(parsed_commit.hashed_reveal, hashed_reveal, + sizeof(parsed_commit.hashed_reveal)); + ret = commit_encode(&parsed_commit, encoded, sizeof(encoded)); + tt_int_op(SR_COMMIT_BASE64_LEN, ==, ret); + tt_mem_op(encoded_commit, OP_EQ, encoded, strlen(encoded_commit)); + } + + done: + ; +} + +/** Setup some SRVs in our SR state. If also_current is set, then set + * both current and previous SRVs. + * Helper of test_vote() and test_sr_compute_srv(). */ +static void +test_sr_setup_srv(int also_current) +{ + sr_srv_t *srv = tor_malloc_zero(sizeof(sr_srv_t)); + srv->num_reveals = 42; + memcpy(srv->value, + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ", + sizeof(srv->value)); + + sr_state_set_previous_srv(srv); + + if (also_current) { + srv = tor_malloc_zero(sizeof(sr_srv_t)); + srv->num_reveals = 128; + memcpy(srv->value, + "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", + sizeof(srv->value)); + + sr_state_set_current_srv(srv); + } +} + +/* Test anything that has to do with SR protocol and vote. */ +static void +test_vote(void *arg) +{ + int ret; + time_t now = time(NULL); + sr_commit_t *our_commit = NULL; + + (void) arg; + + { /* Setup a minimal dirauth environment for this test */ + init_authority_state(); + /* Set ourself in reveal phase so we can parse the reveal value in the + * vote as well. */ + set_sr_phase(SR_PHASE_REVEAL); + } + + /* Generate our commit object and validate it has the appropriate field + * that we can then use to build a representation that we'll find in a + * vote coming from the network. */ + { + sr_commit_t *saved_commit; + our_commit = sr_generate_our_commit(now, mock_cert); + tt_assert(our_commit); + sr_state_add_commit(our_commit); + /* Make sure it's there. */ + saved_commit = sr_state_get_commit(our_commit->rsa_identity_fpr); + tt_assert(saved_commit); + } + + /* Also setup the SRVs */ + test_sr_setup_srv(1); + + { /* Now test the vote generation */ + smartlist_t *chunks = smartlist_new(); + smartlist_t *tokens = smartlist_new(); + /* Get our vote line and validate it. */ + char *lines = sr_get_string_for_vote(); + tt_assert(lines); + /* Split the lines. We expect 2 here. */ + ret = smartlist_split_string(chunks, lines, "\n", SPLIT_IGNORE_BLANK, 0); + tt_int_op(ret, ==, 4); + tt_str_op(smartlist_get(chunks, 0), OP_EQ, "shared-rand-participate"); + /* Get our commitment line and will validate it agains our commit. The + * format is as follow: + * "shared-rand-commitment" SP identity SP algname SP COMMIT [SP REVEAL] NL + */ + char *commit_line = smartlist_get(chunks, 1); + tt_assert(commit_line); + ret = smartlist_split_string(tokens, commit_line, " ", 0, 0); + tt_int_op(ret, ==, 5); + tt_str_op(smartlist_get(tokens, 0), OP_EQ, "shared-rand-commit"); + tt_str_op(smartlist_get(tokens, 1), OP_EQ, + crypto_digest_algorithm_get_name(DIGEST_SHA3_256)); + tt_str_op(smartlist_get(tokens, 2), OP_EQ, + our_commit->rsa_identity_fpr); + tt_str_op(smartlist_get(tokens, 3), OP_EQ, our_commit->encoded_commit); + tt_str_op(smartlist_get(tokens, 4), OP_EQ, our_commit->encoded_reveal); + + /* Finally, does this vote line creates a valid commit object? */ + smartlist_t *args = smartlist_new(); + smartlist_add(args, smartlist_get(tokens, 1)); + smartlist_add(args, smartlist_get(tokens, 2)); + smartlist_add(args, smartlist_get(tokens, 3)); + smartlist_add(args, smartlist_get(tokens, 4)); + sr_commit_t *parsed_commit = sr_parse_commit(args); + tt_assert(parsed_commit); + tt_mem_op(parsed_commit, ==, our_commit, sizeof(*our_commit)); + + /* minor cleanup */ + SMARTLIST_FOREACH(tokens, char *, s, tor_free(s)); + smartlist_clear(tokens); + + /* Now test the previous SRV */ + char *prev_srv_line = smartlist_get(chunks, 2); + tt_assert(prev_srv_line); + ret = smartlist_split_string(tokens, prev_srv_line, " ", 0, 0); + tt_int_op(ret, ==, 3); + tt_str_op(smartlist_get(tokens, 0), OP_EQ, "shared-rand-previous-value"); + tt_str_op(smartlist_get(tokens, 1), OP_EQ, "42"); + tt_str_op(smartlist_get(tokens, 2), OP_EQ, + "WlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlo="); + + /* minor cleanup */ + SMARTLIST_FOREACH(tokens, char *, s, tor_free(s)); + smartlist_clear(tokens); + + /* Now test the current SRV */ + char *current_srv_line = smartlist_get(chunks, 3); + tt_assert(current_srv_line); + ret = smartlist_split_string(tokens, current_srv_line, " ", 0, 0); + tt_int_op(ret, ==, 3); + tt_str_op(smartlist_get(tokens, 0), OP_EQ, "shared-rand-current-value"); + tt_str_op(smartlist_get(tokens, 1), OP_EQ, "128"); + tt_str_op(smartlist_get(tokens, 2), OP_EQ, + "Tk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk4="); + + /* Clean up */ + sr_commit_free(parsed_commit); + SMARTLIST_FOREACH(chunks, char *, s, tor_free(s)); + smartlist_free(chunks); + SMARTLIST_FOREACH(tokens, char *, s, tor_free(s)); + smartlist_free(tokens); + smartlist_clear(args); + smartlist_free(args); + } + + done: + sr_commit_free(our_commit); +} + +const char *sr_state_str = "Version 1\n" + "ValidUntil 2666-04-20 07:16:00\n" + "ValidAfter 2666-04-19 07:16:00\n" + "Commit sha3-256 FA3CEC2C99DC68D3166B9B6E4FA21A4026C2AB1C " + "7M8GdubCAAdh7WUG0DiwRyxTYRKji7HATa7LLJEZ/UAAAAAAVmfUSg== " + "AAAAAFZn1EojfIheIw42bjK3VqkpYyjsQFSbv/dxNna3Q8hUEPKpOw==\n" + "Commit sha3-256 41E89EDFBFBA44983E21F18F2230A4ECB5BFB543 " + "17aUsYuMeRjd2N1r8yNyg7aHqRa6gf4z7QPoxxAZbp0AAAAAVmfUSg==\n" + "Commit sha3-256 36637026573A04110CF3E6B1D201FB9A98B88734 " + "DDDYtripvdOU+XPEUm5xpU64d9IURSds1xSwQsgeB8oAAAAAVmfUSg==\n" + "SharedRandCurrentValue 3 8dWeW12KEzTGEiLGgO1UVJ7Z91CekoRcxt6Q9KhnOFI=\n" + "SharedRandPreviousValue 4 qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqo=\n"; + +/** Create an SR disk state, parse it and validate that the parsing went + * well. Yes! */ +static void +test_state_load_from_disk(void *arg) +{ + int ret; + char *dir = tor_strdup(get_fname("test_sr_state")); + char *sr_state_path = tor_strdup(get_fname("test_sr_state/sr_state")); + sr_state_t *the_sr_state = NULL; + + (void) arg; + + /* First try with a nonexistent path. */ + ret = disk_state_load_from_disk_impl("NONEXISTENTNONEXISTENT"); + tt_assert(ret == -ENOENT); + + /* Now create a mock state directory and state file */ +#ifdef _WIN32 + ret = mkdir(dir); +#else + ret = mkdir(dir, 0700); +#endif + tt_assert(ret == 0); + ret = write_str_to_file(sr_state_path, sr_state_str, 0); + tt_assert(ret == 0); + + /* Try to load the directory itself. Should fail. */ + ret = disk_state_load_from_disk_impl(dir); + tt_assert(ret == -EINVAL); + + /* State should be non-existent at this point. */ + the_sr_state = get_sr_state(); + tt_assert(!the_sr_state); + + /* Now try to load the correct file! */ + ret = disk_state_load_from_disk_impl(sr_state_path); + tt_assert(ret == 0); + + /* Check the content of the state */ + /* XXX check more deeply!!! */ + the_sr_state = get_sr_state(); + tt_assert(the_sr_state); + tt_assert(the_sr_state->version == 1); + tt_assert(digestmap_size(the_sr_state->commits) == 3); + tt_assert(the_sr_state->current_srv); + tt_assert(the_sr_state->current_srv->num_reveals == 3); + tt_assert(the_sr_state->previous_srv); + + /* XXX Now also try loading corrupted state files and make sure parsing + fails */ + + done: + tor_free(dir); + tor_free(sr_state_path); +} + +/** Generate three specially crafted commits (based on the test + * vector at sr_srv_calc_ref.py). Helper of test_sr_compute_srv(). */ +static void +test_sr_setup_commits(void) +{ + time_t now = time(NULL); + sr_commit_t *commit_a, *commit_b, *commit_c, *commit_d; + sr_commit_t *place_holder = tor_malloc_zero(sizeof(*place_holder)); + authority_cert_t *auth_cert = NULL; + + { /* Setup a minimal dirauth environment for this test */ + or_options_t *options = get_options_mutable(); + + auth_cert = authority_cert_parse_from_string(AUTHORITY_CERT_1, NULL); + tt_assert(auth_cert); + + options->AuthoritativeDir = 1; + tt_int_op(0, ==, load_ed_keys(options, now)); + } + + /* Generate three dummy commits according to sr_srv_calc_ref.py . Then + register them to the SR state. Also register a fourth commit 'd' with no + reveal info, to make sure that it will get ignored during SRV + calculation. */ + + { /* Commit from auth 'a' */ + commit_a = sr_generate_our_commit(now, auth_cert); + tt_assert(commit_a); + + /* Do some surgery on the commit */ + strlcpy(commit_a->rsa_identity_fpr, + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + sizeof(commit_a->rsa_identity_fpr)); + strlcpy(commit_a->encoded_reveal, + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + sizeof(commit_a->encoded_reveal)); + memcpy(commit_a->hashed_reveal, + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + sizeof(commit_a->hashed_reveal)); + } + + { /* Commit from auth 'b' */ + commit_b = sr_generate_our_commit(now, auth_cert); + tt_assert(commit_b); + + /* Do some surgery on the commit */ + strlcpy(commit_b->rsa_identity_fpr, + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + sizeof(commit_b->rsa_identity_fpr)); + strlcpy(commit_b->encoded_reveal, + "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + sizeof(commit_b->encoded_reveal)); + memcpy(commit_b->hashed_reveal, + "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + sizeof(commit_b->hashed_reveal)); + } + + { /* Commit from auth 'c' */ + commit_c = sr_generate_our_commit(now, auth_cert); + tt_assert(commit_c); + + /* Do some surgery on the commit */ + strlcpy(commit_c->rsa_identity_fpr, + "ccccccccccccccccccccccccccccccccccccccccccccccccc", + sizeof(commit_c->rsa_identity_fpr)); + strlcpy(commit_c->encoded_reveal, + "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", + sizeof(commit_c->encoded_reveal)); + memcpy(commit_c->hashed_reveal, + "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", + sizeof(commit_c->hashed_reveal)); + } + + { /* Commit from auth 'd' */ + commit_d = sr_generate_our_commit(now, auth_cert); + tt_assert(commit_d); + + /* Do some surgery on the commit */ + strlcpy(commit_d->rsa_identity_fpr, + "ddddddddddddddddddddddddddddddddddddddddddddddddd", + sizeof(commit_d->rsa_identity_fpr)); + strlcpy(commit_d->encoded_reveal, + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD", + sizeof(commit_d->encoded_reveal)); + memcpy(commit_d->hashed_reveal, + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD", + sizeof(commit_d->hashed_reveal)); + /* Clean up its reveal info */ + memcpy(place_holder, commit_d, sizeof(*place_holder)); + memset(commit_d->encoded_reveal, 0, sizeof(commit_d->encoded_reveal)); + tt_assert(!commit_has_reveal_value(commit_d)); + } + + /* Register commits to state (during commit phase) */ + set_sr_phase(SR_PHASE_COMMIT); + save_commit_to_state(commit_a); + save_commit_to_state(commit_b); + save_commit_to_state(commit_c); + save_commit_to_state(commit_d); + tt_int_op(digestmap_size(get_sr_state()->commits), ==, 4); + + /* Now during REVEAL phase save commit D by restoring its reveal. */ + set_sr_phase(SR_PHASE_REVEAL); + save_commit_to_state(place_holder); + tt_str_op(commit_d->encoded_reveal, OP_EQ, + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"); + /* Go back to an empty encoded reveal value. */ + memset(commit_d->encoded_reveal, 0, sizeof(commit_d->encoded_reveal)); + memset(commit_d->random_number, 0, sizeof(commit_d->random_number)); + tt_assert(!commit_has_reveal_value(commit_d)); + + done: + return; +} + +/** Verify that the SRV generation procedure is proper by testing it against + * the test vector from ./sr_srv_calc_ref.py. */ +static void +test_sr_compute_srv(void *arg) +{ + (void) arg; + sr_srv_t *current_srv = NULL; + +#define SRV_TEST_VECTOR \ + "2A9B1D6237DAB312A40F575DA85C147663E7ED3F80E9555395F15B515C74253D" + + MOCK(trusteddirserver_get_by_v3_auth_digest, + trusteddirserver_get_by_v3_auth_digest_m); + + init_authority_state(); + + /* Setup the commits for this unittest */ + test_sr_setup_commits(); + test_sr_setup_srv(0); + + /* Now switch to reveal phase */ + set_sr_phase(SR_PHASE_REVEAL); + + /* Compute the SRV */ + sr_compute_srv(); + + /* Check the result against the test vector */ + current_srv = sr_state_get_current_srv(); + tt_assert(current_srv); + tt_int_op(current_srv->num_reveals, ==, 3); + tt_str_op(hex_str((char*)current_srv->value, 32), + ==, + SRV_TEST_VECTOR); + + done: + ; +} + +/** Return a minimal vote document with a current SRV value set to + * srv. */ +static networkstatus_t * +get_test_vote_with_curr_srv(const char *srv) +{ + networkstatus_t *vote = tor_malloc_zero(sizeof(networkstatus_t)); + + vote->type = NS_TYPE_VOTE; + vote->sr_info.participate = 1; + vote->sr_info.current_srv = tor_malloc_zero(sizeof(sr_srv_t)); + vote->sr_info.current_srv->num_reveals = 42; + memcpy(vote->sr_info.current_srv->value, + srv, + sizeof(vote->sr_info.current_srv->value)); + + return vote; +} + +/* Mock function to return the value located in the options instead of the + * consensus so we can modify it at will. */ +static networkstatus_t * +mock_networkstatus_get_latest_consensus(void) +{ + return mock_consensus; +} + +/* Test the function that picks the right SRV given a bunch of votes. Make sure + * that the function returns an SRV iff the majority/agreement requirements are + * met. */ +static void +test_sr_get_majority_srv_from_votes(void *arg) +{ + sr_srv_t *chosen_srv; + smartlist_t *votes = smartlist_new(); + +#define SRV_1 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +#define SRV_2 "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" + + (void) arg; + + init_authority_state(); + /* Make sure our SRV is fresh so we can consider the super majority with + * the consensus params of number of agreements needed. */ + sr_state_set_fresh_srv(); + + /* The test relies on the dirauth list being initialized. */ + clear_dir_servers(); + add_default_trusted_dir_authorities(V3_DIRINFO); + tt_int_op(get_n_authorities(V3_DIRINFO), ==, 9); + + { /* Prepare voting environment with just a single vote. */ + networkstatus_t *vote = get_test_vote_with_curr_srv(SRV_1); + smartlist_add(votes, vote); + } + + /* Since it's only one vote with an SRV, it should not achieve majority and + hence no SRV will be returned. */ + chosen_srv = get_majority_srv_from_votes(votes, 1); + tt_assert(!chosen_srv); + + { /* Now put in 8 more votes. Let SRV_1 have majority. */ + int i; + /* Now 7 votes believe in SRV_1 */ + for (i = 0; i < 6; i++) { + networkstatus_t *vote = get_test_vote_with_curr_srv(SRV_1); + smartlist_add(votes, vote); + } + /* and 2 votes believe in SRV_2 */ + for (i = 0; i < 2; i++) { + networkstatus_t *vote = get_test_vote_with_curr_srv(SRV_2); + smartlist_add(votes, vote); + } + + tt_int_op(smartlist_len(votes), ==, 9); + } + + /* Now we achieve majority for SRV_1, but not the AuthDirNumSRVAgreements + requirement. So still not picking an SRV. */ + chosen_srv = get_majority_srv_from_votes(votes, 1); + tt_assert(!chosen_srv); + + /* We will now lower the AuthDirNumSRVAgreements requirement by tweaking the + * consensus parameter and we will try again. This time it should work. */ + { + char *my_net_params; + /* Set a dummy consensus parameter in every vote */ + SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, vote) { + vote->net_params = smartlist_new(); + smartlist_split_string(vote->net_params, + "AuthDirNumSRVAgreements=7", NULL, 0, 0); + } SMARTLIST_FOREACH_END(vote); + + /* Pretend you are making a consensus out of the votes */ + mock_consensus = tor_malloc_zero(sizeof(networkstatus_t)); + mock_consensus->net_params = smartlist_new(); + my_net_params = dirvote_compute_params(votes, 66, smartlist_len(votes)); + smartlist_split_string(mock_consensus->net_params, my_net_params, " ", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + } + + /* Use our fake consensus for finding consensus parameters. */ + MOCK(networkstatus_get_latest_consensus, + mock_networkstatus_get_latest_consensus); + chosen_srv = get_majority_srv_from_votes(votes, 1); + tt_assert(chosen_srv); + tt_int_op(chosen_srv->num_reveals, ==, 42); + tt_mem_op(chosen_srv->value, OP_EQ, SRV_1, sizeof(chosen_srv->value)); + + done: + SMARTLIST_FOREACH(votes, networkstatus_t *, vote, + networkstatus_vote_free(vote)); + smartlist_free(votes); + networkstatus_vote_free(mock_consensus); + UNMOCK(networkstatus_get_latest_consensus); +} + +static void +test_utils(void *arg) +{ + (void) arg; + + /* Testing srv_dup(). */ + { + sr_srv_t *srv = NULL, *dup_srv = NULL; + const char *srv_value = + "1BDB7C3E973936E4D13A49F37C859B3DC69C429334CF9412E3FEF6399C52D47A"; + srv = tor_malloc_zero(sizeof(*srv)); + srv->num_reveals = 42; + memcpy(srv->value, srv_value, sizeof(srv->value)); + dup_srv = srv_dup(srv); + tt_assert(dup_srv); + tt_int_op(dup_srv->num_reveals, ==, srv->num_reveals); + tt_mem_op(dup_srv->value, OP_EQ, srv->value, sizeof(srv->value)); + tor_free(srv); + tor_free(dup_srv); + } + + /* Testing commitments_are_the_same(). Currently, the check is to test the + * value of the encoded commit so let's make sure that actually works. */ + { + /* Payload of 55 bytes that is the length of + * sr_commit_t->encoded_commit. */ + const char *payload = + "\x5d\xb9\x60\xb6\xcc\x51\x68\x52\x31\xd9\x88\x88\x71\x71\xe0\x30" + "\x59\x55\x7f\xcd\x61\xc0\x4b\x05\xb8\xcd\xc1\x48\xe9\xcd\x16\x1f" + "\x70\x15\x0c\xfc\xd3\x1a\x75\xd0\x93\x6c\xc4\xe0\x5c\xbe\xe2\x18" + "\xc7\xaf\x72\xb6\x7c\x9b\x52"; + sr_commit_t commit1, commit2; + memcpy(commit1.encoded_commit, payload, sizeof(commit1.encoded_commit)); + memcpy(commit2.encoded_commit, payload, sizeof(commit2.encoded_commit)); + tt_int_op(commitments_are_the_same(&commit1, &commit2), ==, 1); + /* Let's corrupt one of them. */ + memset(commit1.encoded_commit, 'A', sizeof(commit1.encoded_commit)); + tt_int_op(commitments_are_the_same(&commit1, &commit2), ==, 0); + } + + /* Testing commit_is_authoritative(). */ + { + crypto_pk_t *k = crypto_pk_new(); + char fp[FINGERPRINT_LEN + 1]; + sr_commit_t commit; + + tt_assert(!crypto_pk_generate_key(k)); + + tt_int_op(0, ==, crypto_pk_get_fingerprint(k, fp, 0)); + memcpy(fp, commit.rsa_identity_fpr, sizeof(fp)); + tt_int_op(commit_is_authoritative(&commit, fp), ==, 1); + /* Change the pubkey. */ + memset(commit.rsa_identity_fpr, 0, sizeof(commit.rsa_identity_fpr)); + tt_int_op(commit_is_authoritative(&commit, fp), ==, 0); + } + + /* Testing get_phase_str(). */ + { + tt_str_op(get_phase_str(SR_PHASE_REVEAL), ==, "reveal"); + tt_str_op(get_phase_str(SR_PHASE_COMMIT), ==, "commit"); + } + + /* Testing phase transition */ + { + init_authority_state(); + set_sr_phase(SR_PHASE_COMMIT); + tt_int_op(is_phase_transition(SR_PHASE_REVEAL), ==, 1); + tt_int_op(is_phase_transition(SR_PHASE_COMMIT), ==, 0); + set_sr_phase(SR_PHASE_REVEAL); + tt_int_op(is_phase_transition(SR_PHASE_REVEAL), ==, 0); + tt_int_op(is_phase_transition(SR_PHASE_COMMIT), ==, 1); + /* Junk. */ + tt_int_op(is_phase_transition(42), ==, 1); + } + + done: + return; +} + +static void +test_state_transition(void *arg) +{ + sr_state_t *state = NULL; + time_t now = time(NULL); + + (void) arg; + + { /* Setup a minimal dirauth environment for this test */ + init_authority_state(); + state = get_sr_state(); + tt_assert(state); + } + + /* Test our state reset for a new protocol run. */ + { + /* Add a commit to the state so we can test if the reset cleans the + * commits. Also, change all params that we expect to be updated. */ + sr_commit_t *commit = sr_generate_our_commit(now, mock_cert); + tt_assert(commit); + sr_state_add_commit(commit); + tt_int_op(digestmap_size(state->commits), ==, 1); + /* Let's test our delete feature. */ + sr_state_delete_commits(); + tt_int_op(digestmap_size(state->commits), ==, 0); + /* Add it back so we can continue the rest of the test because after + * deletiong our commit will be freed so generate a new one. */ + commit = sr_generate_our_commit(now, mock_cert); + tt_assert(commit); + sr_state_add_commit(commit); + tt_int_op(digestmap_size(state->commits), ==, 1); + state->n_reveal_rounds = 42; + state->n_commit_rounds = 43; + state->n_protocol_runs = 44; + reset_state_for_new_protocol_run(now); + tt_int_op(state->n_reveal_rounds, ==, 0); + tt_int_op(state->n_commit_rounds, ==, 0); + tt_u64_op(state->n_protocol_runs, ==, 45); + tt_int_op(digestmap_size(state->commits), ==, 0); + } + + /* Test SRV rotation in our state. */ + { + sr_srv_t *cur, *prev; + test_sr_setup_srv(1); + cur = sr_state_get_current_srv(); + tt_assert(cur); + /* After, current srv should be the previous and then set to NULL. */ + state_rotate_srv(); + prev = sr_state_get_previous_srv(); + tt_assert(prev == cur); + tt_assert(!sr_state_get_current_srv()); + } + + /* New protocol run. */ + { + sr_srv_t *cur; + /* Setup some new SRVs so we can confirm that a new protocol run + * actually makes them rotate and compute new ones. */ + test_sr_setup_srv(1); + cur = sr_state_get_current_srv(); + tt_assert(cur); + set_sr_phase(SR_PHASE_REVEAL); + MOCK(get_my_v3_authority_cert, get_my_v3_authority_cert_m); + new_protocol_run(now); + UNMOCK(get_my_v3_authority_cert); + /* Rotation happened. */ + tt_assert(sr_state_get_previous_srv() == cur); + /* We are going into COMMIT phase so we had to rotate our SRVs. Usually + * our current SRV would be NULL but a new protocol run should make us + * compute a new SRV. */ + tt_assert(sr_state_get_current_srv()); + /* Also, make sure we did change the current. */ + tt_assert(sr_state_get_current_srv() != cur); + /* We should have our commitment alone. */ + tt_int_op(digestmap_size(state->commits), ==, 1); + tt_int_op(state->n_reveal_rounds, ==, 0); + tt_int_op(state->n_commit_rounds, ==, 0); + /* 46 here since we were at 45 just before. */ + tt_u64_op(state->n_protocol_runs, ==, 46); + } + + /* Cleanup of SRVs. */ + { + sr_state_clean_srvs(); + tt_assert(!sr_state_get_current_srv()); + tt_assert(!sr_state_get_previous_srv()); + } + + done: + return; +} + +static void +test_keep_commit(void *arg) +{ + char fp[FINGERPRINT_LEN + 1]; + sr_commit_t *commit = NULL, *dup_commit = NULL; + sr_state_t *state; + time_t now = time(NULL); + + (void) arg; + + { /* Setup a minimal dirauth environment for this test */ + crypto_pk_t *k = crypto_pk_new(); + /* Have a key that is not the one from our commit. */ + tt_int_op(0, ==, crypto_pk_generate_key(k)); + tt_int_op(0, ==, crypto_pk_get_fingerprint(k, fp, 0)); + init_authority_state(); + state = get_sr_state(); + } + + /* Test this very important function that tells us if we should keep a + * commit or not in our state. Most of it depends on the phase and what's + * in the commit so we'll change the commit as we go. */ + commit = sr_generate_our_commit(now, mock_cert); + tt_assert(commit); + /* Set us in COMMIT phase for starter. */ + set_sr_phase(SR_PHASE_COMMIT); + /* We should never keep a commit from a non authoritative authority. */ + tt_int_op(should_keep_commit(commit, fp, SR_PHASE_COMMIT), ==, 0); + /* This should NOT be kept because it has a reveal value in it. */ + tt_assert(commit_has_reveal_value(commit)); + tt_int_op(should_keep_commit(commit, commit->rsa_identity_fpr, + SR_PHASE_COMMIT), ==, 0); + /* Add it to the state which should return to not keep it. */ + sr_state_add_commit(commit); + tt_int_op(should_keep_commit(commit, commit->rsa_identity_fpr, + SR_PHASE_COMMIT), ==, 0); + /* Remove it from state so we can continue our testing. */ + digestmap_remove(state->commits, commit->rsa_identity_fpr); + /* Let's remove our reveal value which should make it OK to keep it. */ + memset(commit->encoded_reveal, 0, sizeof(commit->encoded_reveal)); + tt_int_op(should_keep_commit(commit, commit->rsa_identity_fpr, + SR_PHASE_COMMIT), ==, 1); + + /* Let's reset our commit and go into REVEAL phase. */ + sr_commit_free(commit); + commit = sr_generate_our_commit(now, mock_cert); + tt_assert(commit); + /* Dup the commit so we have one with and one without a reveal value. */ + dup_commit = tor_malloc_zero(sizeof(*dup_commit)); + memcpy(dup_commit, commit, sizeof(*dup_commit)); + memset(dup_commit->encoded_reveal, 0, sizeof(dup_commit->encoded_reveal)); + set_sr_phase(SR_PHASE_REVEAL); + /* We should never keep a commit from a non authoritative authority. */ + tt_int_op(should_keep_commit(commit, fp, SR_PHASE_REVEAL), ==, 0); + /* We shouldn't accept a commit that is not in our state. */ + tt_int_op(should_keep_commit(commit, commit->rsa_identity_fpr, + SR_PHASE_REVEAL), ==, 0); + /* Important to add the commit _without_ the reveal here. */ + sr_state_add_commit(dup_commit); + tt_int_op(digestmap_size(state->commits), ==, 1); + /* Our commit should be valid that is authoritative, contains a reveal, be + * in the state and commitment and reveal values match. */ + tt_int_op(should_keep_commit(commit, commit->rsa_identity_fpr, + SR_PHASE_REVEAL), ==, 1); + /* The commit shouldn't be kept if it's not verified that is no matchin + * hashed reveal. */ + { + /* Let's save the hash reveal so we can restore it. */ + sr_commit_t place_holder; + memcpy(place_holder.hashed_reveal, commit->hashed_reveal, + sizeof(place_holder.hashed_reveal)); + memset(commit->hashed_reveal, 0, sizeof(commit->hashed_reveal)); + tt_int_op(should_keep_commit(commit, commit->rsa_identity_fpr, + SR_PHASE_REVEAL), ==, 0); + memcpy(commit->hashed_reveal, place_holder.hashed_reveal, + sizeof(commit->hashed_reveal)); + } + /* We shouldn't keep a commit that has no reveal. */ + tt_int_op(should_keep_commit(dup_commit, dup_commit->rsa_identity_fpr, + SR_PHASE_REVEAL), ==, 0); + /* We must not keep a commit that is not the same from the commit phase. */ + memset(commit->encoded_commit, 0, sizeof(commit->encoded_commit)); + tt_int_op(should_keep_commit(commit, commit->rsa_identity_fpr, + SR_PHASE_REVEAL), ==, 0); + + done: + sr_commit_free(commit); + sr_commit_free(dup_commit); +} + +static void +test_state_update(void *arg) +{ + time_t commit_phase_time = 1452076000; + time_t reveal_phase_time = 1452086800; + sr_state_t *state; + + (void) arg; + + { + init_authority_state(); + state = get_sr_state(); + set_sr_phase(SR_PHASE_COMMIT); + /* We'll cheat a bit here and reset the creation time of the state which + * will avoid us to compute a valid_after time that fits the commit + * phase. */ + state->valid_after = 0; + state->n_reveal_rounds = 0; + state->n_commit_rounds = 0; + } + + /* We need to mock for the state update function call. */ + MOCK(get_my_v3_authority_cert, get_my_v3_authority_cert_m); + + /* We are in COMMIT phase here and we'll trigger a state update but no + * transition. */ + sr_state_update(commit_phase_time); + tt_int_op(state->valid_after, ==, commit_phase_time); + tt_int_op(state->n_commit_rounds, ==, 1); + tt_int_op(state->phase, ==, SR_PHASE_COMMIT); + tt_int_op(digestmap_size(state->commits), ==, 1); + + /* We are still in the COMMIT phase here but we'll trigger a state + * transition to the REVEAL phase. */ + sr_state_update(reveal_phase_time); + tt_int_op(state->phase, ==, SR_PHASE_REVEAL); + tt_int_op(state->valid_after, ==, reveal_phase_time); + /* Only our commit should be in there. */ + tt_int_op(digestmap_size(state->commits), ==, 1); + tt_int_op(state->n_reveal_rounds, ==, 1); + + /* We can't update a state with a valid after _lower_ than the creation + * time so here it is. */ + sr_state_update(commit_phase_time); + tt_int_op(state->valid_after, ==, reveal_phase_time); + + /* Finally, let's go back in COMMIT phase so we can test the state update + * of a new protocol run. */ + state->valid_after = 0; + sr_state_update(commit_phase_time); + tt_int_op(state->valid_after, ==, commit_phase_time); + tt_int_op(state->n_commit_rounds, ==, 1); + tt_int_op(state->n_reveal_rounds, ==, 0); + tt_u64_op(state->n_protocol_runs, ==, 1); + tt_int_op(state->phase, ==, SR_PHASE_COMMIT); + tt_int_op(digestmap_size(state->commits), ==, 1); + tt_assert(state->current_srv); + + done: + sr_state_free(); + UNMOCK(get_my_v3_authority_cert); +} + +struct testcase_t sr_tests[] = { + { "get_sr_protocol_phase", test_get_sr_protocol_phase, TT_FORK, + NULL, NULL }, + { "sr_commit", test_sr_commit, TT_FORK, + NULL, NULL }, + { "keep_commit", test_keep_commit, TT_FORK, + NULL, NULL }, + { "encoding", test_encoding, TT_FORK, + NULL, NULL }, + { "get_next_valid_after_time", test_get_next_valid_after_time, TT_FORK, + NULL, NULL }, + { "get_state_valid_until_time", test_get_state_valid_until_time, TT_FORK, + NULL, NULL }, + { "vote", test_vote, TT_FORK, + NULL, NULL }, + { "state_load_from_disk", test_state_load_from_disk, TT_FORK, + NULL, NULL }, + { "sr_compute_srv", test_sr_compute_srv, TT_FORK, NULL, NULL }, + { "sr_get_majority_srv_from_votes", test_sr_get_majority_srv_from_votes, + TT_FORK, NULL, NULL }, + { "utils", test_utils, TT_FORK, NULL, NULL }, + { "state_transition", test_state_transition, TT_FORK, NULL, NULL }, + { "state_update", test_state_update, TT_FORK, + NULL, NULL }, + END_OF_TESTCASES +}; From b12d4852efaf6a6a5184abddacb777d30bf9850f Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 3 May 2016 11:47:13 -0400 Subject: [PATCH 08/26] prop250: Add changes file Fixes #16943 Signed-off-by: David Goulet Signed-off-by: George Kadianakis --- changes/bug16943 | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 changes/bug16943 diff --git a/changes/bug16943 b/changes/bug16943 new file mode 100644 index 0000000000..7b96d1aee3 --- /dev/null +++ b/changes/bug16943 @@ -0,0 +1,8 @@ + o Major features (dirauths, security, hidden services): + - Directory authorities can now perform the shared randomness protocol + specified by proposal 250. Using this protocol, directory authorities can + generate a global fresh random number every day. In the future, this + global randomness will be used by hidden services to select their + responsible HSDirs. This release only implements the directory authority + feature; the hidden service side will be implemented in the future as + part of proposal 224 . Resolves ticket #16943 and proposal 250. From c0cec2ffd3d5f965413f56e93f89fabf81a2c3a0 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Mon, 9 May 2016 16:51:32 -0400 Subject: [PATCH 09/26] prop250: change time_t to uint64_t Signed-off-by: David Goulet --- src/or/shared_random.c | 27 ++++++++++----------------- src/or/shared_random.h | 4 ++-- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/or/shared_random.c b/src/or/shared_random.c index e3fe1a9e73..f5f4ccf9f5 100644 --- a/src/or/shared_random.c +++ b/src/or/shared_random.c @@ -144,18 +144,10 @@ commit_log(const sr_commit_t *commit) tor_assert(commit); log_debug(LD_DIR, "SR: Commit from %s", commit->rsa_identity_fpr); - - if (commit->commit_ts >= 0) { - log_debug(LD_DIR, "SR: Commit: [TS: %ld] [Encoded: %s]", - commit->commit_ts, commit->encoded_commit); - } - - if (commit->reveal_ts >= 0) { - log_debug(LD_DIR, "SR: Reveal: [TS: %ld] [Encoded: %s]", - commit->reveal_ts, safe_str(commit->encoded_reveal)); - } else { - log_debug(LD_DIR, "SR: Reveal: UNKNOWN"); - } + log_debug(LD_DIR, "SR: Commit: [TS: %" PRIu64 "] [Encoded: %s]", + commit->commit_ts, commit->encoded_commit); + log_debug(LD_DIR, "SR: Reveal: [TS: %" PRIu64 "] [Encoded: %s]", + commit->reveal_ts, safe_str(commit->encoded_reveal)); } /* Make sure that the commitment and reveal information in commit @@ -172,8 +164,9 @@ verify_commit_and_reveal(const sr_commit_t *commit) /* Check that the timestamps match. */ if (commit->commit_ts != commit->reveal_ts) { - log_warn(LD_BUG, "SR: Commit timestamp %ld doesn't match reveal " - "timestamp %ld", commit->commit_ts, commit->reveal_ts); + log_warn(LD_BUG, "SR: Commit timestamp %" PRIu64 " doesn't match reveal " + "timestamp %" PRIu64, commit->commit_ts, + commit->reveal_ts); goto invalid; } @@ -261,7 +254,7 @@ commit_decode(const char *encoded, sr_commit_t *commit) } /* First is the timestamp (8 bytes). */ - commit->commit_ts = (time_t) tor_ntohll(get_uint64(b64_decoded)); + commit->commit_ts = tor_ntohll(get_uint64(b64_decoded)); offset += sizeof(uint64_t); /* Next is hashed reveal. */ memcpy(commit->hashed_reveal, b64_decoded + offset, @@ -313,7 +306,7 @@ reveal_decode(const char *encoded, sr_commit_t *commit) goto error; } - commit->reveal_ts = (time_t) tor_ntohll(get_uint64(b64_decoded)); + commit->reveal_ts = tor_ntohll(get_uint64(b64_decoded)); /* Copy the last part, the random value. */ memcpy(commit->random_number, b64_decoded + 8, sizeof(commit->random_number)); @@ -371,7 +364,7 @@ commit_encode(const sr_commit_t *commit, char *dst, size_t len) tor_assert(dst); /* First is the timestamp (8 bytes). */ - set_uint64(buf, tor_htonll((uint64_t) commit->commit_ts)); + set_uint64(buf, tor_htonll(commit->commit_ts)); offset += sizeof(uint64_t); /* and then the hashed reveal. */ memcpy(buf + offset, commit->hashed_reveal, diff --git a/src/or/shared_random.h b/src/or/shared_random.h index d52258c6e2..15eed8a25c 100644 --- a/src/or/shared_random.h +++ b/src/or/shared_random.h @@ -77,7 +77,7 @@ typedef struct sr_commit_t { /* Commitment information */ /* Timestamp of reveal. Correspond to TIMESTAMP. */ - time_t reveal_ts; + uint64_t reveal_ts; /* H(REVEAL) as found in COMMIT message. */ char hashed_reveal[DIGEST256_LEN]; /* Base64 encoded COMMIT. We use this to put it in our vote. */ @@ -90,7 +90,7 @@ typedef struct sr_commit_t { * avoiding possible information leaks of our PRNG. */ uint8_t random_number[SR_RANDOM_NUMBER_LEN]; /* Timestamp of commit. Correspond to TIMESTAMP. */ - time_t commit_ts; + uint64_t commit_ts; /* This is the whole reveal message. We use it during verification */ char encoded_reveal[SR_REVEAL_BASE64_LEN + 1]; } sr_commit_t; From 056b6186adeb5ee92d0899f60b5e061bfc11a8ba Mon Sep 17 00:00:00 2001 From: David Goulet Date: Mon, 9 May 2016 18:58:19 -0400 Subject: [PATCH 10/26] prop250: Use RSA identity digest instead of fingerprint The prop250 code used the RSA identity key fingerprint to index commit in a digestmap instead of using the digest. To behavior change except the fact that we are actually using digestmap correctly. Signed-off-by: David Goulet --- src/or/shared_random.c | 68 ++++++++++++++++++----------------- src/or/shared_random.h | 11 ++++-- src/or/shared_random_state.c | 18 +++++----- src/test/test_shared_random.c | 56 +++++++++++++---------------- 4 files changed, 78 insertions(+), 75 deletions(-) diff --git a/src/or/shared_random.c b/src/or/shared_random.c index f5f4ccf9f5..967e1e112d 100644 --- a/src/or/shared_random.c +++ b/src/or/shared_random.c @@ -120,20 +120,19 @@ srv_dup(const sr_srv_t *orig) return dup; } -/* Allocate a new commit object and initializing it with identity +/* Allocate a new commit object and initializing it with rsa_identity * that MUST be provided. The digest algorithm is set to the default one * that is supported. The rest is uninitialized. This never returns NULL. */ static sr_commit_t * -commit_new(const char *rsa_identity_fpr) +commit_new(const char *rsa_identity) { sr_commit_t *commit; - tor_assert(rsa_identity_fpr); + tor_assert(rsa_identity); commit = tor_malloc_zero(sizeof(*commit)); commit->alg = SR_DIGEST_ALG; - strlcpy(commit->rsa_identity_fpr, rsa_identity_fpr, - sizeof(commit->rsa_identity_fpr)); + memcpy(commit->rsa_identity, rsa_identity, sizeof(commit->rsa_identity)); return commit; } @@ -143,7 +142,7 @@ commit_log(const sr_commit_t *commit) { tor_assert(commit); - log_debug(LD_DIR, "SR: Commit from %s", commit->rsa_identity_fpr); + log_debug(LD_DIR, "SR: Commit from %s", sr_commit_get_rsa_fpr(commit)); log_debug(LD_DIR, "SR: Commit: [TS: %" PRIu64 "] [Encoded: %s]", commit->commit_ts, commit->encoded_commit); log_debug(LD_DIR, "SR: Reveal: [TS: %" PRIu64 "] [Encoded: %s]", @@ -160,7 +159,7 @@ verify_commit_and_reveal(const sr_commit_t *commit) tor_assert(commit); log_debug(LD_DIR, "SR: Validating commit from authority %s", - commit->rsa_identity_fpr); + sr_commit_get_rsa_fpr(commit)); /* Check that the timestamps match. */ if (commit->commit_ts != commit->reveal_ts) { @@ -194,7 +193,7 @@ verify_commit_and_reveal(const sr_commit_t *commit) sizeof(received_hashed_reveal))) { log_warn(LD_BUG, "SR: Received reveal value from authority %s " "does't match the commit value.", - commit->rsa_identity_fpr); + sr_commit_get_rsa_fpr(commit)); goto invalid; } } @@ -242,14 +241,14 @@ commit_decode(const char *encoded, sr_commit_t *commit) encoded, strlen(encoded)); if (decoded_len < 0) { log_warn(LD_BUG, "SR: Commit from authority %s can't be decoded.", - commit->rsa_identity_fpr); + sr_commit_get_rsa_fpr(commit)); goto error; } if (decoded_len != SR_COMMIT_LEN) { log_warn(LD_BUG, "SR: Commit from authority %s decoded length doesn't " "match the expected length (%d vs %d).", - commit->rsa_identity_fpr, decoded_len, SR_COMMIT_LEN); + sr_commit_get_rsa_fpr(commit), decoded_len, SR_COMMIT_LEN); goto error; } @@ -295,14 +294,14 @@ reveal_decode(const char *encoded, sr_commit_t *commit) encoded, strlen(encoded)); if (decoded_len < 0) { log_warn(LD_BUG, "SR: Reveal from authority %s can't be decoded.", - commit->rsa_identity_fpr); + sr_commit_get_rsa_fpr(commit)); goto error; } if (decoded_len != SR_REVEAL_LEN) { log_warn(LD_BUG, "SR: Reveal from authority %s decoded length is " "doesn't match the expected length (%d vs %d)", - commit->rsa_identity_fpr, decoded_len, SR_REVEAL_LEN); + sr_commit_get_rsa_fpr(commit), decoded_len, SR_REVEAL_LEN); goto error; } @@ -396,7 +395,7 @@ get_srv_element_from_commit(const sr_commit_t *commit) return NULL; } - tor_asprintf(&element, "%s%s", commit->rsa_identity_fpr, + tor_asprintf(&element, "%s%s", sr_commit_get_rsa_fpr(commit), commit->encoded_reveal); return element; } @@ -465,7 +464,7 @@ get_vote_line_from_commit(const sr_commit_t *commit, sr_phase_t phase) tor_asprintf(&vote_line, "%s %s %s %s\n", commit_ns_str, crypto_digest_algorithm_get_name(commit->alg), - commit->rsa_identity_fpr, + sr_commit_get_rsa_fpr(commit), commit->encoded_commit); break; case SR_PHASE_REVEAL: @@ -479,7 +478,7 @@ get_vote_line_from_commit(const sr_commit_t *commit, sr_phase_t phase) tor_asprintf(&vote_line, "%s %s %s %s %s\n", commit_ns_str, crypto_digest_algorithm_get_name(commit->alg), - commit->rsa_identity_fpr, + sr_commit_get_rsa_fpr(commit), commit->encoded_commit, reveal_str); break; } @@ -567,7 +566,8 @@ commit_is_authoritative(const sr_commit_t *commit, tor_assert(commit); tor_assert(voter_key); - return !strcmp(commit->rsa_identity_fpr, voter_key); + return !memcmp(commit->rsa_identity, voter_key, + sizeof(commit->rsa_identity)); } /* Decide if the newly received commit should be kept depending on @@ -586,7 +586,8 @@ should_keep_commit(const sr_commit_t *commit, const char *voter_key, tor_assert(voter_key); log_debug(LD_DIR, "SR: Inspecting commit from %s (voter: %s)?", - commit->rsa_identity_fpr, voter_key); + sr_commit_get_rsa_fpr(commit), + hex_str(voter_key, DIGEST_LEN)); /* For a commit to be considered, it needs to be authoritative (it should * be the voter's own commit). */ @@ -597,7 +598,7 @@ should_keep_commit(const sr_commit_t *commit, const char *voter_key, /* Check if the authority that voted for commit has already posted * a commit before. */ - saved_commit = sr_state_get_commit(commit->rsa_identity_fpr); + saved_commit = sr_state_get_commit(commit->rsa_identity); switch (phase) { case SR_PHASE_COMMIT: @@ -611,7 +612,8 @@ should_keep_commit(const sr_commit_t *commit, const char *voter_key, if (commit_has_reveal_value(commit)) { log_warn(LD_DIR, "SR: Commit from authority %s has a reveal value " "during COMMIT phase. (voter: %s)", - commit->rsa_identity_fpr, voter_key); + sr_commit_get_rsa_fpr(commit), + hex_str(voter_key, DIGEST_LEN)); goto ignore; } break; @@ -635,7 +637,8 @@ should_keep_commit(const sr_commit_t *commit, const char *voter_key, if (!commitments_are_the_same(commit, saved_commit)) { log_warn(LD_DIR, "SR: Commit from authority %s is different from " "previous commit in our state (voter: %s)", - commit->rsa_identity_fpr, voter_key); + sr_commit_get_rsa_fpr(commit), + hex_str(voter_key, DIGEST_LEN)); goto ignore; } @@ -652,7 +655,8 @@ should_keep_commit(const sr_commit_t *commit, const char *voter_key, if (verify_commit_and_reveal(commit) < 0) { log_warn(LD_BUG, "SR: Commit from authority %s has an invalid " "reveal value. (voter: %s)", - commit->rsa_identity_fpr, voter_key); + sr_commit_get_rsa_fpr(commit), + hex_str(voter_key, DIGEST_LEN)); goto ignore; } break; @@ -677,7 +681,7 @@ save_commit_during_reveal_phase(const sr_commit_t *commit) tor_assert(commit); /* Get the commit from our state. */ - saved_commit = sr_state_get_commit(commit->rsa_identity_fpr); + saved_commit = sr_state_get_commit(commit->rsa_identity); tor_assert(saved_commit); /* Safety net. They can not be different commitments at this point. */ int same_commits = commitments_are_the_same(commit, saved_commit); @@ -868,18 +872,17 @@ sr_commit_t * sr_generate_our_commit(time_t timestamp, const authority_cert_t *my_rsa_cert) { sr_commit_t *commit = NULL; - char fingerprint[FINGERPRINT_LEN+1]; + char digest[DIGEST_LEN]; tor_assert(my_rsa_cert); /* Get our RSA identity fingerprint */ - if (crypto_pk_get_fingerprint(my_rsa_cert->identity_key, - fingerprint, 0) < 0) { + if (crypto_pk_get_digest(my_rsa_cert->identity_key, digest) < 0) { goto error; } /* New commit with our identity key. */ - commit = commit_new(fingerprint); + commit = commit_new(digest); /* Generate the reveal random value */ crypto_strongest_rand(commit->random_number, @@ -1036,7 +1039,7 @@ sr_parse_srv(const smartlist_t *args) sr_commit_t * sr_parse_commit(const smartlist_t *args) { - char *value; + char *value, digest[DIGEST_LEN]; digest_algorithm_t alg; const char *rsa_identity_fpr; sr_commit_t *commit = NULL; @@ -1054,7 +1057,8 @@ sr_parse_commit(const smartlist_t *args) goto error; } - /* Second argument is the RSA fingerprint of the auth */ + /* Second argument is the RSA fingerprint of the auth and turn it into a + * digest value. */ rsa_identity_fpr = smartlist_get(args, 1); if (base16_decode(digest, DIGEST_LEN, rsa_identity_fpr, HEX_DIGEST_LEN) < 0) { @@ -1073,7 +1077,7 @@ sr_parse_commit(const smartlist_t *args) } /* Allocate commit since we have a valid identity now. */ - commit = commit_new(rsa_identity_fpr); + commit = commit_new(digest); /* Third argument is the commitment value base64-encoded. */ value = smartlist_get(args, 2); @@ -1103,7 +1107,7 @@ sr_parse_commit(const smartlist_t *args) void sr_handle_received_commits(smartlist_t *commits, crypto_pk_t *voter_key) { - char rsa_identity_fpr[FINGERPRINT_LEN + 1]; + char rsa_identity[DIGEST_LEN]; tor_assert(voter_key); @@ -1113,7 +1117,7 @@ sr_handle_received_commits(smartlist_t *commits, crypto_pk_t *voter_key) } /* Get the RSA identity fingerprint of this voter */ - if (crypto_pk_get_fingerprint(voter_key, rsa_identity_fpr, 0) < 0) { + if (crypto_pk_get_digest(voter_key, rsa_identity) < 0) { return; } @@ -1121,7 +1125,7 @@ sr_handle_received_commits(smartlist_t *commits, crypto_pk_t *voter_key) /* We won't need the commit in this list anymore, kept or not. */ SMARTLIST_DEL_CURRENT(commits, commit); /* Check if this commit is valid and should be stored in our state. */ - if (!should_keep_commit(commit, rsa_identity_fpr, + if (!should_keep_commit(commit, rsa_identity, sr_state_get_phase())) { sr_commit_free(commit); continue; diff --git a/src/or/shared_random.h b/src/or/shared_random.h index 15eed8a25c..573c499efc 100644 --- a/src/or/shared_random.h +++ b/src/or/shared_random.h @@ -71,8 +71,8 @@ typedef struct sr_commit_t { /* Commit owner info */ - /* The RSA identity fingerprint of the authority. */ - char rsa_identity_fpr[FINGERPRINT_LEN + 1]; + /* The RSA identity key of the authority. */ + char rsa_identity[DIGEST_LEN]; /* Commitment information */ @@ -112,11 +112,16 @@ void sr_commit_free(sr_commit_t *commit); void sr_srv_encode(char *dst, const sr_srv_t *srv); /* Private methods (only used by shared_random_state.c): */ +static inline +const char *sr_commit_get_rsa_fpr(const sr_commit_t *commit) +{ + return hex_str((const char *) commit->rsa_identity, + sizeof(commit->rsa_identity)); +} void sr_compute_srv(void); sr_commit_t *sr_generate_our_commit(time_t timestamp, const authority_cert_t *my_rsa_cert); - #ifdef SHARED_RANDOM_PRIVATE /* Encode */ diff --git a/src/or/shared_random_state.c b/src/or/shared_random_state.c index 7c75431040..326b8c9cf0 100644 --- a/src/or/shared_random_state.c +++ b/src/or/shared_random_state.c @@ -218,14 +218,14 @@ commit_add_to_state(sr_commit_t *commit, sr_state_t *state) tor_assert(commit); tor_assert(state); - saved_commit = digestmap_set(state->commits, commit->rsa_identity_fpr, + saved_commit = digestmap_set(state->commits, commit->rsa_identity, commit); if (saved_commit != NULL) { /* This means we already have that commit in our state so adding twice * the same commit is either a code flow error, a corrupted disk state * or some new unknown issue. */ log_warn(LD_DIR, "SR: Commit from %s exists in our state while " - "adding it: '%s'", commit->rsa_identity_fpr, + "adding it: '%s'", sr_commit_get_rsa_fpr(commit), commit->encoded_commit); sr_commit_free(saved_commit); } @@ -562,7 +562,7 @@ disk_state_put_commit_line(const sr_commit_t *commit, config_line_t *line) } tor_asprintf(&line->value, "%s %s %s%s", crypto_digest_algorithm_get_name(commit->alg), - commit->rsa_identity_fpr, + sr_commit_get_rsa_fpr(commit), commit->encoded_commit, reveal_str != NULL ? reveal_str : ""); if (reveal_str != NULL) { @@ -1124,17 +1124,17 @@ sr_state_update(time_t valid_after) } } -/* Return commit object from the given authority digest identity. +/* Return commit object from the given authority digest rsa_identity. * Return NULL if not found. */ sr_commit_t * -sr_state_get_commit(const char *rsa_fpr) +sr_state_get_commit(const char *rsa_identity) { sr_commit_t *commit; - tor_assert(rsa_fpr); + tor_assert(rsa_identity); state_query(SR_STATE_ACTION_GET, SR_STATE_OBJ_COMMIT, - (void *) rsa_fpr, (void *) &commit); + (void *) rsa_identity, (void *) &commit); return commit; } @@ -1150,7 +1150,7 @@ sr_state_add_commit(sr_commit_t *commit) (void *) commit, NULL); log_debug(LD_DIR, "SR: Commit from %s has been added to our state.", - commit->rsa_identity_fpr); + sr_commit_get_rsa_fpr(commit)); } /* Remove all commits from our state. */ @@ -1178,7 +1178,7 @@ sr_state_copy_reveal_info(sr_commit_t *saved_commit, const sr_commit_t *commit) state_query(SR_STATE_ACTION_SAVE, 0, NULL, NULL); log_debug(LD_DIR, "SR: Reveal value learned %s (for commit %s) from %s", saved_commit->encoded_reveal, saved_commit->encoded_commit, - saved_commit->rsa_identity_fpr); + sr_commit_get_rsa_fpr(saved_commit)); } /* Set the fresh SRV flag from our state. This doesn't need to trigger a diff --git a/src/test/test_shared_random.c b/src/test/test_shared_random.c index 112571784f..fbb4414534 100644 --- a/src/test/test_shared_random.c +++ b/src/test/test_shared_random.c @@ -331,7 +331,7 @@ test_sr_commit(void *arg) sr_commit_t *parsed_commit; smartlist_add(args, tor_strdup(crypto_digest_algorithm_get_name(our_commit->alg))); - smartlist_add(args, our_commit->rsa_identity_fpr); + smartlist_add(args, tor_strdup(sr_commit_get_rsa_fpr(our_commit))); smartlist_add(args, our_commit->encoded_commit); smartlist_add(args, our_commit->encoded_reveal); parsed_commit = sr_parse_commit(args); @@ -480,7 +480,7 @@ test_vote(void *arg) tt_assert(our_commit); sr_state_add_commit(our_commit); /* Make sure it's there. */ - saved_commit = sr_state_get_commit(our_commit->rsa_identity_fpr); + saved_commit = sr_state_get_commit(our_commit->rsa_identity); tt_assert(saved_commit); } @@ -508,8 +508,10 @@ test_vote(void *arg) tt_str_op(smartlist_get(tokens, 0), OP_EQ, "shared-rand-commit"); tt_str_op(smartlist_get(tokens, 1), OP_EQ, crypto_digest_algorithm_get_name(DIGEST_SHA3_256)); - tt_str_op(smartlist_get(tokens, 2), OP_EQ, - our_commit->rsa_identity_fpr); + char digest[DIGEST_LEN]; + base16_decode(digest, sizeof(digest), smartlist_get(tokens, 2), + HEX_DIGEST_LEN); + tt_mem_op(digest, ==, our_commit->rsa_identity, sizeof(digest)); tt_str_op(smartlist_get(tokens, 3), OP_EQ, our_commit->encoded_commit); tt_str_op(smartlist_get(tokens, 4), OP_EQ, our_commit->encoded_reveal); @@ -664,9 +666,7 @@ test_sr_setup_commits(void) tt_assert(commit_a); /* Do some surgery on the commit */ - strlcpy(commit_a->rsa_identity_fpr, - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - sizeof(commit_a->rsa_identity_fpr)); + memset(commit_a->rsa_identity, 'A', sizeof(commit_a->rsa_identity)); strlcpy(commit_a->encoded_reveal, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", sizeof(commit_a->encoded_reveal)); @@ -680,9 +680,7 @@ test_sr_setup_commits(void) tt_assert(commit_b); /* Do some surgery on the commit */ - strlcpy(commit_b->rsa_identity_fpr, - "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - sizeof(commit_b->rsa_identity_fpr)); + memset(commit_b->rsa_identity, 'B', sizeof(commit_b->rsa_identity)); strlcpy(commit_b->encoded_reveal, "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", sizeof(commit_b->encoded_reveal)); @@ -696,9 +694,7 @@ test_sr_setup_commits(void) tt_assert(commit_c); /* Do some surgery on the commit */ - strlcpy(commit_c->rsa_identity_fpr, - "ccccccccccccccccccccccccccccccccccccccccccccccccc", - sizeof(commit_c->rsa_identity_fpr)); + memset(commit_c->rsa_identity, 'C', sizeof(commit_c->rsa_identity)); strlcpy(commit_c->encoded_reveal, "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", sizeof(commit_c->encoded_reveal)); @@ -712,9 +708,7 @@ test_sr_setup_commits(void) tt_assert(commit_d); /* Do some surgery on the commit */ - strlcpy(commit_d->rsa_identity_fpr, - "ddddddddddddddddddddddddddddddddddddddddddddddddd", - sizeof(commit_d->rsa_identity_fpr)); + memset(commit_d->rsa_identity, 'D', sizeof(commit_d->rsa_identity)); strlcpy(commit_d->encoded_reveal, "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD", sizeof(commit_d->encoded_reveal)); @@ -946,17 +940,17 @@ test_utils(void *arg) /* Testing commit_is_authoritative(). */ { crypto_pk_t *k = crypto_pk_new(); - char fp[FINGERPRINT_LEN + 1]; + char digest[DIGEST_LEN]; sr_commit_t commit; tt_assert(!crypto_pk_generate_key(k)); - tt_int_op(0, ==, crypto_pk_get_fingerprint(k, fp, 0)); - memcpy(fp, commit.rsa_identity_fpr, sizeof(fp)); - tt_int_op(commit_is_authoritative(&commit, fp), ==, 1); + tt_int_op(0, ==, crypto_pk_get_digest(k, digest)); + memcpy(commit.rsa_identity, digest, sizeof(commit.rsa_identity)); + tt_int_op(commit_is_authoritative(&commit, digest), ==, 1); /* Change the pubkey. */ - memset(commit.rsa_identity_fpr, 0, sizeof(commit.rsa_identity_fpr)); - tt_int_op(commit_is_authoritative(&commit, fp), ==, 0); + memset(commit.rsa_identity, 0, sizeof(commit.rsa_identity)); + tt_int_op(commit_is_authoritative(&commit, digest), ==, 0); } /* Testing get_phase_str(). */ @@ -1105,17 +1099,17 @@ test_keep_commit(void *arg) tt_int_op(should_keep_commit(commit, fp, SR_PHASE_COMMIT), ==, 0); /* This should NOT be kept because it has a reveal value in it. */ tt_assert(commit_has_reveal_value(commit)); - tt_int_op(should_keep_commit(commit, commit->rsa_identity_fpr, + tt_int_op(should_keep_commit(commit, commit->rsa_identity, SR_PHASE_COMMIT), ==, 0); /* Add it to the state which should return to not keep it. */ sr_state_add_commit(commit); - tt_int_op(should_keep_commit(commit, commit->rsa_identity_fpr, + tt_int_op(should_keep_commit(commit, commit->rsa_identity, SR_PHASE_COMMIT), ==, 0); /* Remove it from state so we can continue our testing. */ - digestmap_remove(state->commits, commit->rsa_identity_fpr); + digestmap_remove(state->commits, commit->rsa_identity); /* Let's remove our reveal value which should make it OK to keep it. */ memset(commit->encoded_reveal, 0, sizeof(commit->encoded_reveal)); - tt_int_op(should_keep_commit(commit, commit->rsa_identity_fpr, + tt_int_op(should_keep_commit(commit, commit->rsa_identity, SR_PHASE_COMMIT), ==, 1); /* Let's reset our commit and go into REVEAL phase. */ @@ -1130,14 +1124,14 @@ test_keep_commit(void *arg) /* We should never keep a commit from a non authoritative authority. */ tt_int_op(should_keep_commit(commit, fp, SR_PHASE_REVEAL), ==, 0); /* We shouldn't accept a commit that is not in our state. */ - tt_int_op(should_keep_commit(commit, commit->rsa_identity_fpr, + tt_int_op(should_keep_commit(commit, commit->rsa_identity, SR_PHASE_REVEAL), ==, 0); /* Important to add the commit _without_ the reveal here. */ sr_state_add_commit(dup_commit); tt_int_op(digestmap_size(state->commits), ==, 1); /* Our commit should be valid that is authoritative, contains a reveal, be * in the state and commitment and reveal values match. */ - tt_int_op(should_keep_commit(commit, commit->rsa_identity_fpr, + tt_int_op(should_keep_commit(commit, commit->rsa_identity, SR_PHASE_REVEAL), ==, 1); /* The commit shouldn't be kept if it's not verified that is no matchin * hashed reveal. */ @@ -1147,17 +1141,17 @@ test_keep_commit(void *arg) memcpy(place_holder.hashed_reveal, commit->hashed_reveal, sizeof(place_holder.hashed_reveal)); memset(commit->hashed_reveal, 0, sizeof(commit->hashed_reveal)); - tt_int_op(should_keep_commit(commit, commit->rsa_identity_fpr, + tt_int_op(should_keep_commit(commit, commit->rsa_identity, SR_PHASE_REVEAL), ==, 0); memcpy(commit->hashed_reveal, place_holder.hashed_reveal, sizeof(commit->hashed_reveal)); } /* We shouldn't keep a commit that has no reveal. */ - tt_int_op(should_keep_commit(dup_commit, dup_commit->rsa_identity_fpr, + tt_int_op(should_keep_commit(dup_commit, dup_commit->rsa_identity, SR_PHASE_REVEAL), ==, 0); /* We must not keep a commit that is not the same from the commit phase. */ memset(commit->encoded_commit, 0, sizeof(commit->encoded_commit)); - tt_int_op(should_keep_commit(commit, commit->rsa_identity_fpr, + tt_int_op(should_keep_commit(commit, commit->rsa_identity, SR_PHASE_REVEAL), ==, 0); done: From 8ac88f6f9739c5fd5fd5b47af63592faa80b8307 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 10 May 2016 11:37:28 -0400 Subject: [PATCH 11/26] prop250: Add a valid flag to sr_commit_t We assert on it using the ASSERT_COMMIT_VALID() macro in critical places where we use them expecting a commit to be valid. Signed-off-by: David Goulet --- src/or/shared_random.c | 9 +++++++++ src/or/shared_random.h | 5 +++++ src/or/shared_random_state.c | 3 +++ 3 files changed, 17 insertions(+) diff --git a/src/or/shared_random.c b/src/or/shared_random.c index 967e1e112d..e56a23918e 100644 --- a/src/or/shared_random.c +++ b/src/or/shared_random.c @@ -700,6 +700,8 @@ save_commit_to_state(sr_commit_t *commit) { sr_phase_t phase = sr_state_get_phase(); + ASSERT_COMMIT_VALID(commit); + switch (phase) { case SR_PHASE_COMMIT: /* During commit phase, just save any new authoritative commit */ @@ -914,6 +916,8 @@ sr_generate_our_commit(time_t timestamp, const authority_cert_t *my_rsa_cert) log_debug(LD_DIR, "SR: Generated our commitment:"); commit_log(commit); + /* Our commit better be valid :). */ + commit->valid = 1; return commit; error: @@ -942,6 +946,8 @@ sr_compute_srv(void) /* We must make a list of commit ordered by authority fingerprint in * ascending order as specified by proposal 250. */ DIGESTMAP_FOREACH(state_commits, key, sr_commit_t *, c) { + /* Extra safety net, make sure we have valid commit before using it. */ + ASSERT_COMMIT_VALID(c); smartlist_add(commits, c); } DIGESTMAP_FOREACH_END; smartlist_sort(commits, compare_reveal_); @@ -1130,6 +1136,9 @@ sr_handle_received_commits(smartlist_t *commits, crypto_pk_t *voter_key) sr_commit_free(commit); continue; } + /* Ok, we have a valid commit now that we are about to put in our state. + * so flag it valid from now on. */ + commit->valid = 1; /* Everything lines up: save this commit to state then! */ save_commit_to_state(commit); } SMARTLIST_FOREACH_END(commit); diff --git a/src/or/shared_random.h b/src/or/shared_random.h index 573c499efc..f89f47a11b 100644 --- a/src/or/shared_random.h +++ b/src/or/shared_random.h @@ -48,6 +48,9 @@ #define SR_SRV_VALUE_BASE64_LEN \ (((DIGEST256_LEN - 1) / 3) * 4 + 4) +/* Assert if commit valid flag is not set. */ +#define ASSERT_COMMIT_VALID(c) tor_assert((c)->valid) + /* Protocol phase. */ typedef enum { /* Commitment phase */ @@ -68,6 +71,8 @@ typedef struct sr_srv_t { typedef struct sr_commit_t { /* Hashing algorithm used. */ digest_algorithm_t alg; + /* Indicate if this commit has been verified thus valid. */ + unsigned int valid:1; /* Commit owner info */ diff --git a/src/or/shared_random_state.c b/src/or/shared_random_state.c index 326b8c9cf0..115d95410b 100644 --- a/src/or/shared_random_state.c +++ b/src/or/shared_random_state.c @@ -410,6 +410,9 @@ disk_state_parse_commits(sr_state_t *state, * fingerprint that we don't know about so it shouldn't be used. */ continue; } + /* We consider parseable commit from our disk state to be valid because + * they need to be in the first place to get in there. */ + commit->valid = 1; /* Add commit to our state pointer. */ commit_add_to_state(commit, state); From 0f27d92e4c420cae8fff0ab889ad8494461d6f8e Mon Sep 17 00:00:00 2001 From: David Goulet Date: Wed, 11 May 2016 10:21:34 -0400 Subject: [PATCH 12/26] prop250: Add version to Commit line in vote and state Signed-off-by: David Goulet --- src/or/shared_random.c | 38 ++++++++++++++++++++++++------------ src/or/shared_random_state.c | 3 ++- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/or/shared_random.c b/src/or/shared_random.c index e56a23918e..6e6ff3baa3 100644 --- a/src/or/shared_random.c +++ b/src/or/shared_random.c @@ -97,6 +97,7 @@ #include "router.h" #include "routerlist.h" #include "shared_random_state.h" +#include "util.h" /* String prefix of shared random values in votes/consensuses. */ static const char previous_srv_str[] = "shared-rand-previous-value"; @@ -461,8 +462,9 @@ get_vote_line_from_commit(const sr_commit_t *commit, sr_phase_t phase) switch (phase) { case SR_PHASE_COMMIT: - tor_asprintf(&vote_line, "%s %s %s %s\n", + tor_asprintf(&vote_line, "%s %u %s %s %s\n", commit_ns_str, + SR_PROTO_VERSION, crypto_digest_algorithm_get_name(commit->alg), sr_commit_get_rsa_fpr(commit), commit->encoded_commit); @@ -475,8 +477,9 @@ get_vote_line_from_commit(const sr_commit_t *commit, sr_phase_t phase) sizeof(commit->encoded_reveal))) { reveal_str = ""; } - tor_asprintf(&vote_line, "%s %s %s %s %s\n", + tor_asprintf(&vote_line, "%s %u %s %s %s %s\n", commit_ns_str, + SR_PROTO_VERSION, crypto_digest_algorithm_get_name(commit->alg), sr_commit_get_rsa_fpr(commit), commit->encoded_commit, reveal_str); @@ -1040,22 +1043,33 @@ sr_parse_srv(const smartlist_t *args) * allocated commit object. NULL is returned on error. * * The commit's data is in args and the order matters very much: - * algname, RSA fingerprint, commit value[, reveal value] + * version, algname, RSA fingerprint, commit value[, reveal value] */ sr_commit_t * sr_parse_commit(const smartlist_t *args) { + uint32_t version; char *value, digest[DIGEST_LEN]; digest_algorithm_t alg; const char *rsa_identity_fpr; sr_commit_t *commit = NULL; - if (smartlist_len(args) < 3) { + if (smartlist_len(args) < 4) { goto error; } - /* First argument is the algorithm. */ + /* First is the version number of the SR protocol which indicates at which + * version that commit was created. */ value = smartlist_get(args, 0); + version = (uint32_t) tor_parse_ulong(value, 10, 1, UINT32_MAX, NULL, NULL); + if (version > SR_PROTO_VERSION) { + log_info(LD_DIR, "SR: Commit version %" PRIu32 " (%s) is not supported.", + version, escaped(value)); + goto error; + } + + /* Second is the algorithm. */ + value = smartlist_get(args, 1); alg = crypto_digest_algorithm_parse_name(value); if (alg != SR_DIGEST_ALG) { log_warn(LD_BUG, "SR: Commit algorithm %s is not recognized.", @@ -1063,9 +1077,9 @@ sr_parse_commit(const smartlist_t *args) goto error; } - /* Second argument is the RSA fingerprint of the auth and turn it into a + /* Third argument is the RSA fingerprint of the auth and turn it into a * digest value. */ - rsa_identity_fpr = smartlist_get(args, 1); + rsa_identity_fpr = smartlist_get(args, 2); if (base16_decode(digest, DIGEST_LEN, rsa_identity_fpr, HEX_DIGEST_LEN) < 0) { log_warn(LD_DIR, "SR: RSA fingerprint '%s' not decodable", @@ -1085,15 +1099,15 @@ sr_parse_commit(const smartlist_t *args) /* Allocate commit since we have a valid identity now. */ commit = commit_new(digest); - /* Third argument is the commitment value base64-encoded. */ - value = smartlist_get(args, 2); + /* Fourth argument is the commitment value base64-encoded. */ + value = smartlist_get(args, 3); if (commit_decode(value, commit) < 0) { goto error; } - /* (Optional) Fourth argument is the revealed value. */ - if (smartlist_len(args) > 3) { - value = smartlist_get(args, 3); + /* (Optional) Fifth argument is the revealed value. */ + if (smartlist_len(args) > 4) { + value = smartlist_get(args, 4); if (reveal_decode(value, commit) < 0) { goto error; } diff --git a/src/or/shared_random_state.c b/src/or/shared_random_state.c index 115d95410b..9700bf5b26 100644 --- a/src/or/shared_random_state.c +++ b/src/or/shared_random_state.c @@ -563,7 +563,8 @@ disk_state_put_commit_line(const sr_commit_t *commit, config_line_t *line) /* Add extra whitespace so we can format the line correctly. */ tor_asprintf(&reveal_str, " %s", commit->encoded_reveal); } - tor_asprintf(&line->value, "%s %s %s%s", + tor_asprintf(&line->value, "%u %s %s %s%s", + SR_PROTO_VERSION, crypto_digest_algorithm_get_name(commit->alg), sr_commit_get_rsa_fpr(commit), commit->encoded_commit, From 545b77e2f8fac6cf5909bb6b69b52d9cb4f8f397 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Wed, 11 May 2016 16:02:18 -0400 Subject: [PATCH 13/26] prop250: Only trust known authority when computing SRV Signed-off-by: David Goulet --- src/or/shared_random.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/or/shared_random.c b/src/or/shared_random.c index 6e6ff3baa3..d409d109ac 100644 --- a/src/or/shared_random.c +++ b/src/or/shared_random.c @@ -951,6 +951,16 @@ sr_compute_srv(void) DIGESTMAP_FOREACH(state_commits, key, sr_commit_t *, c) { /* Extra safety net, make sure we have valid commit before using it. */ ASSERT_COMMIT_VALID(c); + /* Let's not use a commit from an authority that we don't know. It's + * possible that an authority could be removed during a protocol run so + * that commit value should never be used in the SRV computation. */ + if (trusteddirserver_get_by_v3_auth_digest(c->rsa_identity) == NULL) { + log_warn(LD_DIR, "SR: Fingerprint %s is not from a recognized " + "authority. Discarding commit for the SRV computation.", + sr_commit_get_rsa_fpr(c)); + continue; + } + /* We consider this commit valid. */ smartlist_add(commits, c); } DIGESTMAP_FOREACH_END; smartlist_sort(commits, compare_reveal_); From edea0441477cf7f1293ce848abf59b3f1033b28a Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Fri, 13 May 2016 13:42:33 -0400 Subject: [PATCH 14/26] prop250: Sort smartlist before you get most frequent SRV. Signed-off-by: David Goulet --- src/or/shared_random.c | 10 ++++++++++ src/test/test_shared_random.c | 6 +++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/or/shared_random.c b/src/or/shared_random.c index d409d109ac..c5a62a754a 100644 --- a/src/or/shared_random.c +++ b/src/or/shared_random.c @@ -779,6 +779,15 @@ smartlist_get_most_frequent_srv(const smartlist_t *sl, int *count_out) return smartlist_get_most_frequent_(sl, compare_srvs_, count_out); } +/** Compare two SRVs. Used in smartlist sorting. */ +static int +compare_srv_(const void **_a, const void **_b) +{ + const sr_srv_t *a = *_a, *b = *_b; + return fast_memcmp(a->value, b->value, + sizeof(a->value)); +} + /* Using a list of votes, return the SRV object from them that has * been voted by the majority of dirauths. If current is set, we look * for the current SRV value else the previous one. The returned pointer is @@ -813,6 +822,7 @@ get_majority_srv_from_votes(const smartlist_t *votes, int current) smartlist_add(srv_list, srv_tmp); } SMARTLIST_FOREACH_END(v); + smartlist_sort(srv_list, compare_srv_); most_frequent_srv = smartlist_get_most_frequent_srv(srv_list, &count); if (!most_frequent_srv) { goto end; diff --git a/src/test/test_shared_random.c b/src/test/test_shared_random.c index fbb4414534..5a9c123d2a 100644 --- a/src/test/test_shared_random.c +++ b/src/test/test_shared_random.c @@ -844,7 +844,7 @@ test_sr_get_majority_srv_from_votes(void *arg) { /* Now put in 8 more votes. Let SRV_1 have majority. */ int i; /* Now 7 votes believe in SRV_1 */ - for (i = 0; i < 6; i++) { + for (i = 0; i < 3; i++) { networkstatus_t *vote = get_test_vote_with_curr_srv(SRV_1); smartlist_add(votes, vote); } @@ -853,6 +853,10 @@ test_sr_get_majority_srv_from_votes(void *arg) networkstatus_t *vote = get_test_vote_with_curr_srv(SRV_2); smartlist_add(votes, vote); } + for (i = 0; i < 3; i++) { + networkstatus_t *vote = get_test_vote_with_curr_srv(SRV_1); + smartlist_add(votes, vote); + } tt_int_op(smartlist_len(votes), ==, 9); } From 63ca307127f70f2df3da20d50ecc545939658991 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 17 May 2016 15:05:32 -0400 Subject: [PATCH 15/26] prop250: Improve log messages Signed-off-by: David Goulet --- src/or/shared_random.c | 6 +++--- src/or/shared_random_state.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/or/shared_random.c b/src/or/shared_random.c index c5a62a754a..15c33af60c 100644 --- a/src/or/shared_random.c +++ b/src/or/shared_random.c @@ -1102,8 +1102,8 @@ sr_parse_commit(const smartlist_t *args) rsa_identity_fpr = smartlist_get(args, 2); if (base16_decode(digest, DIGEST_LEN, rsa_identity_fpr, HEX_DIGEST_LEN) < 0) { - log_warn(LD_DIR, "SR: RSA fingerprint '%s' not decodable", - rsa_identity_fpr); + log_warn(LD_DIR, "SR: RSA fingerprint %s not decodable", + escaped(rsa_identity_fpr)); goto error; } /* Let's make sure, for extra safety, that this fingerprint is known to @@ -1112,7 +1112,7 @@ sr_parse_commit(const smartlist_t *args) if (trusteddirserver_get_by_v3_auth_digest(digest) == NULL) { log_warn(LD_DIR, "SR: Fingerprint %s is not from a recognized " "authority. Discarding commit.", - rsa_identity_fpr); + escaped(rsa_identity_fpr)); goto error; } diff --git a/src/or/shared_random_state.c b/src/or/shared_random_state.c index 9700bf5b26..cce0c99939 100644 --- a/src/or/shared_random_state.c +++ b/src/or/shared_random_state.c @@ -1122,7 +1122,7 @@ sr_state_update(time_t valid_after) char tbuf[ISO_TIME_LEN + 1]; format_iso_time(tbuf, valid_after); log_info(LD_DIR, "SR: State prepared for new voting period (%s). " - "Current phase is %s (%d/%d).", + "Current phase is %s (%d commit & %d reveal rounds).", tbuf, get_phase_str(sr_state->phase), sr_state->n_commit_rounds, sr_state->n_reveal_rounds); } From cd858d78a7d04fa64b9af3d602c435154acd284f Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 17 May 2016 15:13:13 -0400 Subject: [PATCH 16/26] prop250: Sort commits in lexicographical order in votes Signed-off-by: David Goulet --- src/or/shared_random.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/or/shared_random.c b/src/or/shared_random.c index 15c33af60c..23c2b1417c 100644 --- a/src/or/shared_random.c +++ b/src/or/shared_random.c @@ -1205,11 +1205,21 @@ sr_get_string_for_vote(void) /* In our vote we include every commitment in our permanent state. */ state_commits = sr_state_get_commits(); + smartlist_t *state_commit_vote_lines = smartlist_new(); DIGESTMAP_FOREACH(state_commits, key, const sr_commit_t *, commit) { char *line = get_vote_line_from_commit(commit, sr_state_get_phase()); - smartlist_add(chunks, line); + smartlist_add(state_commit_vote_lines, line); } DIGESTMAP_FOREACH_END; + /* Sort the commit strings by version (string, not numeric), algorithm, + * and fingerprint. This makes sure the commit lines in votes are in a + * recognisable, stable order. */ + smartlist_sort_strings(state_commit_vote_lines); + + /* Now add the sorted list of commits to the vote */ + smartlist_add_all(chunks, state_commit_vote_lines); + smartlist_free(state_commit_vote_lines); + /* Add the SRV value(s) if any. */ { char *srv_lines = get_ns_str_from_sr_values(sr_state_get_previous_srv(), From d43646e191dec6f800f63acb6a6861190b77d86f Mon Sep 17 00:00:00 2001 From: David Goulet Date: Thu, 12 May 2016 13:30:07 -0400 Subject: [PATCH 17/26] prop250: Fix unit tests about the RSA fingerprint check Code has been changed so every RSA fingerprint for a commit in our state is validated before being used. This fixes the unit tests by mocking one of the key function and updating the hardcoded state string. Also, fix a time parsing overflow on platforms with 32bit time_t Signed-off-by: David Goulet Signed-off-by: George Kadianakis --- src/or/routerlist.c | 4 +- src/or/routerlist.h | 3 +- src/test/test_shared_random.c | 70 +++++++++++++++++++++++++++-------- 3 files changed, 58 insertions(+), 19 deletions(-) diff --git a/src/or/routerlist.c b/src/or/routerlist.c index bdb3d490c7..4d55b2e44f 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -1494,8 +1494,8 @@ router_digest_is_fallback_dir(const char *digest) * v3 identity key hashes to digest, or NULL if no such authority * is known. */ -dir_server_t * -trusteddirserver_get_by_v3_auth_digest(const char *digest) +MOCK_IMPL(dir_server_t *, +trusteddirserver_get_by_v3_auth_digest, (const char *digest)) { if (!trusted_dir_servers) return NULL; diff --git a/src/or/routerlist.h b/src/or/routerlist.h index be242d6e87..17adade5d3 100644 --- a/src/or/routerlist.h +++ b/src/or/routerlist.h @@ -52,7 +52,8 @@ dir_server_t *router_get_trusteddirserver_by_digest(const char *d); dir_server_t *router_get_fallback_dirserver_by_digest( const char *digest); int router_digest_is_fallback_dir(const char *digest); -dir_server_t *trusteddirserver_get_by_v3_auth_digest(const char *d); +MOCK_DECL(dir_server_t *, trusteddirserver_get_by_v3_auth_digest, + (const char *d)); const routerstatus_t *router_pick_trusteddirserver(dirinfo_type_t type, int flags); const routerstatus_t *router_pick_fallback_dirserver(dirinfo_type_t type, diff --git a/src/test/test_shared_random.c b/src/test/test_shared_random.c index 5a9c123d2a..18d45b5eba 100644 --- a/src/test/test_shared_random.c +++ b/src/test/test_shared_random.c @@ -24,6 +24,18 @@ get_my_v3_authority_cert_m(void) return mock_cert; } +static dir_server_t ds; + +static dir_server_t * +trusteddirserver_get_by_v3_auth_digest_m(const char *digest) +{ + (void) digest; + /* The shared random code only need to know if a valid pointer to a dir + * server object has been found so this is safe because it won't use the + * pointer at all never. */ + return &ds; +} + /* Setup a minimal dirauth environment by initializing the SR state and * making sure the options are set to be an authority directory. */ static void @@ -265,6 +277,9 @@ test_sr_commit(void *arg) (void) arg; + MOCK(trusteddirserver_get_by_v3_auth_digest, + trusteddirserver_get_by_v3_auth_digest_m); + { /* Setup a minimal dirauth environment for this test */ or_options_t *options = get_options_mutable(); @@ -329,6 +344,7 @@ test_sr_commit(void *arg) * takes from a vote line and see if we can parse it correctly. */ { sr_commit_t *parsed_commit; + smartlist_add(args, tor_strdup("1")); smartlist_add(args, tor_strdup(crypto_digest_algorithm_get_name(our_commit->alg))); smartlist_add(args, tor_strdup(sr_commit_get_rsa_fpr(our_commit))); @@ -336,10 +352,13 @@ test_sr_commit(void *arg) smartlist_add(args, our_commit->encoded_reveal); parsed_commit = sr_parse_commit(args); tt_assert(parsed_commit); - /* That parsed commit should be _EXACTLY_ like our original commit. */ + /* That parsed commit should be _EXACTLY_ like our original commit (we + * have to explicitly set the valid flag though). */ + parsed_commit->valid = 1; tt_mem_op(parsed_commit, OP_EQ, our_commit, sizeof(*parsed_commit)); /* Cleanup */ tor_free(smartlist_get(args, 0)); /* strdup here. */ + tor_free(smartlist_get(args, 1)); /* strdup here. */ smartlist_clear(args); sr_commit_free(parsed_commit); } @@ -347,6 +366,7 @@ test_sr_commit(void *arg) done: smartlist_free(args); sr_commit_free(our_commit); + UNMOCK(trusteddirserver_get_by_v3_auth_digest); } /* Test the encoding and decoding function for commit and reveal values. */ @@ -464,6 +484,9 @@ test_vote(void *arg) (void) arg; + MOCK(trusteddirserver_get_by_v3_auth_digest, + trusteddirserver_get_by_v3_auth_digest_m); + { /* Setup a minimal dirauth environment for this test */ init_authority_state(); /* Set ourself in reveal phase so we can parse the reveal value in the @@ -499,21 +522,23 @@ test_vote(void *arg) tt_str_op(smartlist_get(chunks, 0), OP_EQ, "shared-rand-participate"); /* Get our commitment line and will validate it agains our commit. The * format is as follow: - * "shared-rand-commitment" SP identity SP algname SP COMMIT [SP REVEAL] NL + * "shared-rand-commitment" SP version SP algname SP identity + * SP COMMIT [SP REVEAL] NL */ char *commit_line = smartlist_get(chunks, 1); tt_assert(commit_line); ret = smartlist_split_string(tokens, commit_line, " ", 0, 0); - tt_int_op(ret, ==, 5); + tt_int_op(ret, ==, 6); tt_str_op(smartlist_get(tokens, 0), OP_EQ, "shared-rand-commit"); - tt_str_op(smartlist_get(tokens, 1), OP_EQ, + tt_str_op(smartlist_get(tokens, 1), OP_EQ, "1"); + tt_str_op(smartlist_get(tokens, 2), OP_EQ, crypto_digest_algorithm_get_name(DIGEST_SHA3_256)); char digest[DIGEST_LEN]; - base16_decode(digest, sizeof(digest), smartlist_get(tokens, 2), + base16_decode(digest, sizeof(digest), smartlist_get(tokens, 3), HEX_DIGEST_LEN); tt_mem_op(digest, ==, our_commit->rsa_identity, sizeof(digest)); - tt_str_op(smartlist_get(tokens, 3), OP_EQ, our_commit->encoded_commit); - tt_str_op(smartlist_get(tokens, 4), OP_EQ, our_commit->encoded_reveal); + tt_str_op(smartlist_get(tokens, 4), OP_EQ, our_commit->encoded_commit); + tt_str_op(smartlist_get(tokens, 5), OP_EQ, our_commit->encoded_reveal); /* Finally, does this vote line creates a valid commit object? */ smartlist_t *args = smartlist_new(); @@ -521,8 +546,12 @@ test_vote(void *arg) smartlist_add(args, smartlist_get(tokens, 2)); smartlist_add(args, smartlist_get(tokens, 3)); smartlist_add(args, smartlist_get(tokens, 4)); + smartlist_add(args, smartlist_get(tokens, 5)); sr_commit_t *parsed_commit = sr_parse_commit(args); tt_assert(parsed_commit); + /* Set valid flag explicitly here to compare since it's not set by + * simply parsing the commit. */ + parsed_commit->valid = 1; tt_mem_op(parsed_commit, ==, our_commit, sizeof(*our_commit)); /* minor cleanup */ @@ -565,20 +594,22 @@ test_vote(void *arg) done: sr_commit_free(our_commit); + UNMOCK(trusteddirserver_get_by_v3_auth_digest); } const char *sr_state_str = "Version 1\n" - "ValidUntil 2666-04-20 07:16:00\n" - "ValidAfter 2666-04-19 07:16:00\n" - "Commit sha3-256 FA3CEC2C99DC68D3166B9B6E4FA21A4026C2AB1C " + "TorVersion 0.2.9.0-alpha-dev\n" + "ValidAfter 2037-04-19 07:16:00\n" + "ValidUntil 2037-04-20 07:16:00\n" + "Commit 1 sha3-256 FA3CEC2C99DC68D3166B9B6E4FA21A4026C2AB1C " "7M8GdubCAAdh7WUG0DiwRyxTYRKji7HATa7LLJEZ/UAAAAAAVmfUSg== " "AAAAAFZn1EojfIheIw42bjK3VqkpYyjsQFSbv/dxNna3Q8hUEPKpOw==\n" - "Commit sha3-256 41E89EDFBFBA44983E21F18F2230A4ECB5BFB543 " + "Commit 1 sha3-256 41E89EDFBFBA44983E21F18F2230A4ECB5BFB543 " "17aUsYuMeRjd2N1r8yNyg7aHqRa6gf4z7QPoxxAZbp0AAAAAVmfUSg==\n" - "Commit sha3-256 36637026573A04110CF3E6B1D201FB9A98B88734 " + "Commit 1 sha3-256 36637026573A04110CF3E6B1D201FB9A98B88734 " "DDDYtripvdOU+XPEUm5xpU64d9IURSds1xSwQsgeB8oAAAAAVmfUSg==\n" - "SharedRandCurrentValue 3 8dWeW12KEzTGEiLGgO1UVJ7Z91CekoRcxt6Q9KhnOFI=\n" - "SharedRandPreviousValue 4 qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqo=\n"; + "SharedRandPreviousValue 4 qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqo=\n" + "SharedRandCurrentValue 3 8dWeW12KEzTGEiLGgO1UVJ7Z91CekoRcxt6Q9KhnOFI=\n"; /** Create an SR disk state, parse it and validate that the parsing went * well. Yes! */ @@ -592,6 +623,9 @@ test_state_load_from_disk(void *arg) (void) arg; + MOCK(trusteddirserver_get_by_v3_auth_digest, + trusteddirserver_get_by_v3_auth_digest_m); + /* First try with a nonexistent path. */ ret = disk_state_load_from_disk_impl("NONEXISTENTNONEXISTENT"); tt_assert(ret == -ENOENT); @@ -608,7 +642,7 @@ test_state_load_from_disk(void *arg) /* Try to load the directory itself. Should fail. */ ret = disk_state_load_from_disk_impl(dir); - tt_assert(ret == -EINVAL); + tt_assert(ret == -EISDIR); /* State should be non-existent at this point. */ the_sr_state = get_sr_state(); @@ -634,6 +668,7 @@ test_state_load_from_disk(void *arg) done: tor_free(dir); tor_free(sr_state_path); + UNMOCK(trusteddirserver_get_by_v3_auth_digest); } /** Generate three specially crafted commits (based on the test @@ -754,6 +789,9 @@ test_sr_compute_srv(void *arg) #define SRV_TEST_VECTOR \ "2A9B1D6237DAB312A40F575DA85C147663E7ED3F80E9555395F15B515C74253D" + MOCK(trusteddirserver_get_by_v3_auth_digest, + trusteddirserver_get_by_v3_auth_digest_m); + MOCK(trusteddirserver_get_by_v3_auth_digest, trusteddirserver_get_by_v3_auth_digest_m); @@ -778,7 +816,7 @@ test_sr_compute_srv(void *arg) SRV_TEST_VECTOR); done: - ; + UNMOCK(trusteddirserver_get_by_v3_auth_digest); } /** Return a minimal vote document with a current SRV value set to From 5fe9a50c310b8af4c9447475ec9e3748b24984f0 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 17 May 2016 16:10:20 -0400 Subject: [PATCH 18/26] prop250: Pass the dst length to sr_srv_encode() Signed-off-by: David Goulet --- src/or/shared_random.c | 12 +++++++----- src/or/shared_random.h | 2 +- src/or/shared_random_state.c | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/or/shared_random.c b/src/or/shared_random.c index 23c2b1417c..8427b680a7 100644 --- a/src/or/shared_random.c +++ b/src/or/shared_random.c @@ -437,7 +437,7 @@ generate_srv(const char *hashed_reveals, uint8_t reveal_num, { /* Debugging. */ char srv_hash_encoded[SR_SRV_VALUE_BASE64_LEN + 1]; - sr_srv_encode(srv_hash_encoded, srv); + sr_srv_encode(srv_hash_encoded, sizeof(srv_hash_encoded), srv); log_debug(LD_DIR, "SR: Generated SRV: %s", srv_hash_encoded); } return srv; @@ -504,7 +504,7 @@ srv_to_ns_string(const sr_srv_t *srv, const char *key) tor_assert(srv); tor_assert(key); - sr_srv_encode(srv_hash_encoded, srv); + sr_srv_encode(srv_hash_encoded, sizeof(srv_hash_encoded), srv); tor_asprintf(&srv_str, "%s %d %s\n", key, srv->num_reveals, srv_hash_encoded); log_debug(LD_DIR, "SR: Consensus SRV line: %s", srv_str); @@ -839,7 +839,7 @@ get_majority_srv_from_votes(const smartlist_t *votes, int current) { /* Debugging */ char encoded[SR_SRV_VALUE_BASE64_LEN + 1]; - sr_srv_encode(encoded, the_srv); + sr_srv_encode(encoded, sizeof(encoded), the_srv); log_debug(LD_DIR, "SR: Chosen SRV by majority: %s (%d votes)", encoded, count); } @@ -853,7 +853,7 @@ get_majority_srv_from_votes(const smartlist_t *votes, int current) /* Encode the given shared random value and put it in dst. Destination * buffer must be at least SR_SRV_VALUE_BASE64_LEN plus the NULL byte. */ void -sr_srv_encode(char *dst, const sr_srv_t *srv) +sr_srv_encode(char *dst, size_t dst_len, const sr_srv_t *srv) { int ret; /* Extra byte for the NULL terminated char. */ @@ -861,12 +861,14 @@ sr_srv_encode(char *dst, const sr_srv_t *srv) tor_assert(dst); tor_assert(srv); + tor_assert(dst_len >= sizeof(buf)); ret = base64_encode(buf, sizeof(buf), (const char *) srv->value, sizeof(srv->value), 0); /* Always expect the full length without the NULL byte. */ tor_assert(ret == (sizeof(buf) - 1)); - strlcpy(dst, buf, sizeof(buf)); + tor_assert(ret <= (int) dst_len); + strlcpy(dst, buf, dst_len); } /* Free a commit object. */ diff --git a/src/or/shared_random.h b/src/or/shared_random.h index f89f47a11b..4b16d26702 100644 --- a/src/or/shared_random.h +++ b/src/or/shared_random.h @@ -114,7 +114,7 @@ sr_srv_t *sr_parse_srv(const smartlist_t *args); char *sr_get_string_for_vote(void); char *sr_get_string_for_consensus(const smartlist_t *votes); void sr_commit_free(sr_commit_t *commit); -void sr_srv_encode(char *dst, const sr_srv_t *srv); +void sr_srv_encode(char *dst, size_t dst_len, const sr_srv_t *srv); /* Private methods (only used by shared_random_state.c): */ static inline diff --git a/src/or/shared_random_state.c b/src/or/shared_random_state.c index cce0c99939..705c586ae5 100644 --- a/src/or/shared_random_state.c +++ b/src/or/shared_random_state.c @@ -589,7 +589,7 @@ disk_state_put_srv_line(const sr_srv_t *srv, config_line_t *line) if (srv == NULL) { return; } - sr_srv_encode(encoded, srv); + sr_srv_encode(encoded, sizeof(encoded), srv); tor_asprintf(&line->value, "%d %s", srv->num_reveals, encoded); } From f6f4668b1d0dec1a9afc25ef9d7fcd884cc55608 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Tue, 24 May 2016 13:56:39 +0300 Subject: [PATCH 19/26] prop250: Don't reject votes containing commits of unknown dirauths. Instead just ignore those commits. Squash this commit with 33b2ade. --- src/or/shared_random.c | 19 ++++++++++--------- src/test/test_shared_random.c | 8 ++++---- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/or/shared_random.c b/src/or/shared_random.c index 8427b680a7..7da6daf62f 100644 --- a/src/or/shared_random.c +++ b/src/or/shared_random.c @@ -599,6 +599,16 @@ should_keep_commit(const sr_commit_t *commit, const char *voter_key, goto ignore; } + /* Let's make sure, for extra safety, that this fingerprint is known to + * us. Even though this comes from a vote, doesn't hurt to be + * extracareful. */ + if (trusteddirserver_get_by_v3_auth_digest(commit->rsa_identity) == NULL) { + log_warn(LD_DIR, "SR: Fingerprint %s is not from a recognized " + "authority. Discarding commit.", + escaped(commit->rsa_identity)); + goto ignore; + } + /* Check if the authority that voted for commit has already posted * a commit before. */ saved_commit = sr_state_get_commit(commit->rsa_identity); @@ -1108,15 +1118,6 @@ sr_parse_commit(const smartlist_t *args) escaped(rsa_identity_fpr)); goto error; } - /* Let's make sure, for extra safety, that this fingerprint is known to - * us. Even though this comes from a vote, doesn't hurt to be - * extracareful. */ - if (trusteddirserver_get_by_v3_auth_digest(digest) == NULL) { - log_warn(LD_DIR, "SR: Fingerprint %s is not from a recognized " - "authority. Discarding commit.", - escaped(rsa_identity_fpr)); - goto error; - } /* Allocate commit since we have a valid identity now. */ commit = commit_new(digest); diff --git a/src/test/test_shared_random.c b/src/test/test_shared_random.c index 18d45b5eba..dcd71e01aa 100644 --- a/src/test/test_shared_random.c +++ b/src/test/test_shared_random.c @@ -277,9 +277,6 @@ test_sr_commit(void *arg) (void) arg; - MOCK(trusteddirserver_get_by_v3_auth_digest, - trusteddirserver_get_by_v3_auth_digest_m); - { /* Setup a minimal dirauth environment for this test */ or_options_t *options = get_options_mutable(); @@ -366,7 +363,6 @@ test_sr_commit(void *arg) done: smartlist_free(args); sr_commit_free(our_commit); - UNMOCK(trusteddirserver_get_by_v3_auth_digest); } /* Test the encoding and decoding function for commit and reveal values. */ @@ -1121,6 +1117,9 @@ test_keep_commit(void *arg) (void) arg; + MOCK(trusteddirserver_get_by_v3_auth_digest, + trusteddirserver_get_by_v3_auth_digest_m); + { /* Setup a minimal dirauth environment for this test */ crypto_pk_t *k = crypto_pk_new(); /* Have a key that is not the one from our commit. */ @@ -1199,6 +1198,7 @@ test_keep_commit(void *arg) done: sr_commit_free(commit); sr_commit_free(dup_commit); + UNMOCK(trusteddirserver_get_by_v3_auth_digest); } static void From ebbff3174059f0c1d0fb51a39f97de453ea055f4 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Tue, 24 May 2016 13:58:20 +0300 Subject: [PATCH 20/26] prop250: Silence a logging message. LOG_NOTICE is a bit too much for that one. --- src/or/shared_random.c | 12 ++++++++++-- src/or/shared_random_state.c | 6 +++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/or/shared_random.c b/src/or/shared_random.c index 7da6daf62f..599273f066 100644 --- a/src/or/shared_random.c +++ b/src/or/shared_random.c @@ -438,7 +438,7 @@ generate_srv(const char *hashed_reveals, uint8_t reveal_num, /* Debugging. */ char srv_hash_encoded[SR_SRV_VALUE_BASE64_LEN + 1]; sr_srv_encode(srv_hash_encoded, sizeof(srv_hash_encoded), srv); - log_debug(LD_DIR, "SR: Generated SRV: %s", srv_hash_encoded); + log_info(LD_DIR, "SR: Generated SRV: %s", srv_hash_encoded); } return srv; } @@ -617,7 +617,15 @@ should_keep_commit(const sr_commit_t *commit, const char *voter_key, case SR_PHASE_COMMIT: /* Already having a commit for an authority so ignore this one. */ if (saved_commit) { - log_debug(LD_DIR, "SR: Ignoring known commit during COMMIT phase."); + /* Receiving known commits should happen naturally since commit phase + lasts multiple rounds. However if the commitment value changes + during commit phase, it might be a bug so log more loudly. */ + if (!commitments_are_the_same(commit, saved_commit)) { + log_info(LD_DIR, "SR: Received altered commit from %s in commit phase.", + sr_commit_get_rsa_fpr(commit)); + } else { + log_debug(LD_DIR, "SR: Ignoring known commit during commit phase."); + } goto ignore; } diff --git a/src/or/shared_random_state.c b/src/or/shared_random_state.c index 705c586ae5..87126f5d62 100644 --- a/src/or/shared_random_state.c +++ b/src/or/shared_random_state.c @@ -720,7 +720,7 @@ disk_state_load_from_disk_impl(const char *fname) state_set(parsed_state); disk_state_set(disk_state); tor_free(content); - log_notice(LD_DIR, "SR: State loaded successfully from file %s", fname); + log_info(LD_DIR, "SR: State loaded successfully from file %s", fname); return 0; error: @@ -1121,8 +1121,8 @@ sr_state_update(time_t valid_after) { /* Debugging. */ char tbuf[ISO_TIME_LEN + 1]; format_iso_time(tbuf, valid_after); - log_info(LD_DIR, "SR: State prepared for new voting period (%s). " - "Current phase is %s (%d commit & %d reveal rounds).", + log_info(LD_DIR, "SR: State prepared for upcoming voting period (%s). " + "Upcoming phase is %s (counters: %d commit & %d reveal rounds).", tbuf, get_phase_str(sr_state->phase), sr_state->n_commit_rounds, sr_state->n_reveal_rounds); } From 899d2b890b35b9772f33054b9ff627dd8186deac Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Wed, 25 May 2016 12:28:40 +0300 Subject: [PATCH 21/26] prop250: Don't use {0} to init static struct -- causes warning on clang. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See ticket #19132 for the clang/llvm warning. Since voting_schedule is a global static struct, it will be initialized to zero even without explicitly initializing it with {0}. This is what the C spec says: If an object that has automatic storage duration is not initialized explicitly, its value is indeterminate. If an object that has static storage duration is not initialized explicitly, then: — if it has pointer type, it is initialized to a null pointer; — if it has arithmetic type, it is initialized to (positive or unsigned) zero; — if it is an aggregate, every member is initialized (recursively) according to these rules; — if it is a union, the first named member is initialized (recursively) according to these rules. --- src/or/dirvote.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/or/dirvote.c b/src/or/dirvote.c index 4eccdd0611..50713318bd 100644 --- a/src/or/dirvote.c +++ b/src/or/dirvote.c @@ -2545,7 +2545,7 @@ get_next_valid_after_time(time_t now) return next_valid_after_time; } -static voting_schedule_t voting_schedule = {0}; +static voting_schedule_t voting_schedule; /** Set voting_schedule to hold the timing for the next vote we should be * doing. */ From e62f3133bb0404b3c70dd59f37b2706bf75eb800 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Thu, 26 May 2016 12:25:01 -0400 Subject: [PATCH 22/26] prop250: Change reveal_num to uint64_t and version to uint32_t Signed-off-by: David Goulet --- src/or/shared_random.c | 26 +++++++++++++------------- src/or/shared_random.h | 6 +++--- src/or/shared_random_state.c | 4 ++-- src/or/shared_random_state.h | 4 ++-- src/test/test_shared_random.c | 3 --- 5 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/or/shared_random.c b/src/or/shared_random.c index 599273f066..5f61170d5e 100644 --- a/src/or/shared_random.c +++ b/src/or/shared_random.c @@ -403,10 +403,10 @@ get_srv_element_from_commit(const sr_commit_t *commit) /* Return a srv object that is built with the construction: * SRV = SHA3-256("shared-random" | INT_8(reveal_num) | - * INT_8(version) | HASHED_REVEALS | previous_SRV) + * INT_4(version) | HASHED_REVEALS | previous_SRV) * This function cannot fail. */ static sr_srv_t * -generate_srv(const char *hashed_reveals, uint8_t reveal_num, +generate_srv(const char *hashed_reveals, uint64_t reveal_num, const sr_srv_t *previous_srv) { char msg[DIGEST256_LEN + SR_SRV_MSG_LEN] = {0}; @@ -418,10 +418,10 @@ generate_srv(const char *hashed_reveals, uint8_t reveal_num, /* Add the invariant token. */ memcpy(msg, SR_SRV_TOKEN, SR_SRV_TOKEN_LEN); offset += SR_SRV_TOKEN_LEN; - set_uint8(msg + offset, reveal_num); - offset += 1; - set_uint8(msg + offset, SR_PROTO_VERSION); - offset += 1; + set_uint64(msg + offset, tor_htonll(reveal_num)); + offset += sizeof(uint64_t); + set_uint32(msg + offset, htonl(SR_PROTO_VERSION)); + offset += sizeof(uint32_t); memcpy(msg + offset, hashed_reveals, DIGEST256_LEN); offset += DIGEST256_LEN; if (previous_srv != NULL) { @@ -505,7 +505,7 @@ srv_to_ns_string(const sr_srv_t *srv, const char *key) tor_assert(key); sr_srv_encode(srv_hash_encoded, sizeof(srv_hash_encoded), srv); - tor_asprintf(&srv_str, "%s %d %s\n", key, + tor_asprintf(&srv_str, "%s %" PRIu64 " %s\n", key, srv->num_reveals, srv_hash_encoded); log_debug(LD_DIR, "SR: Consensus SRV line: %s", srv_str); return srv_str; @@ -962,7 +962,7 @@ sr_generate_our_commit(time_t timestamp, const authority_cert_t *my_rsa_cert) void sr_compute_srv(void) { - size_t reveal_num = 0; + uint64_t reveal_num = 0; char *reveals = NULL; smartlist_t *chunks, *commits; digestmap_t *state_commits; @@ -1019,8 +1019,7 @@ sr_compute_srv(void) SR_DIGEST_ALG)) { goto end; } - tor_assert(reveal_num < UINT8_MAX); - current_srv = generate_srv(hashed_reveals, (uint8_t) reveal_num, + current_srv = generate_srv(hashed_reveals, reveal_num, sr_state_get_previous_srv()); sr_state_set_current_srv(current_srv); /* We have a fresh SRV, flag our state. */ @@ -1042,7 +1041,8 @@ sr_srv_t * sr_parse_srv(const smartlist_t *args) { char *value; - int num_reveals, ok, ret; + int ok, ret; + uint64_t num_reveals; sr_srv_t *srv = NULL; tor_assert(args); @@ -1052,8 +1052,8 @@ sr_parse_srv(const smartlist_t *args) } /* First argument is the number of reveal values */ - num_reveals = (int)tor_parse_long(smartlist_get(args, 0), - 10, 0, INT32_MAX, &ok, NULL); + num_reveals = tor_parse_uint64(smartlist_get(args, 0), + 10, 0, UINT64_MAX, &ok, NULL); if (!ok) { goto end; } diff --git a/src/or/shared_random.h b/src/or/shared_random.h index 4b16d26702..6d68ad79bc 100644 --- a/src/or/shared_random.h +++ b/src/or/shared_random.h @@ -30,9 +30,9 @@ * timestamp and the hashed random number. This adds up to 40 bytes. */ #define SR_REVEAL_LEN (sizeof(uint64_t) + DIGEST256_LEN) /* Size of SRV message length. The construction is has follow: - * "shared-random" | INT_8(reveal_num) | INT_8(version) | PREV_SRV */ + * "shared-random" | INT_8(reveal_num) | INT_4(version) | PREV_SRV */ #define SR_SRV_MSG_LEN \ - (SR_SRV_TOKEN_LEN + 1 + 1 + DIGEST256_LEN) + (SR_SRV_TOKEN_LEN + sizeof(uint64_t) + sizeof(uint32_t) + DIGEST256_LEN) /* Length of base64 encoded commit NOT including the NULL terminated byte. * Formula is taken from base64_encode_size. */ @@ -62,7 +62,7 @@ typedef enum { /* A shared random value (SRV). */ typedef struct sr_srv_t { /* The number of reveal values used to derive this SRV. */ - int num_reveals; + uint64_t num_reveals; /* The actual value. This is the stored result of SHA3-256. */ uint8_t value[DIGEST256_LEN]; } sr_srv_t; diff --git a/src/or/shared_random_state.c b/src/or/shared_random_state.c index 87126f5d62..eeffb1479e 100644 --- a/src/or/shared_random_state.c +++ b/src/or/shared_random_state.c @@ -60,7 +60,7 @@ disk_state_validate_cb(void *old_state, void *state, void *default_state, /* Array of variables that are saved to disk as a persistent state. */ static config_var_t state_vars[] = { - V(Version, INT, "0"), + V(Version, UINT, "0"), V(TorVersion, STRING, NULL), V(ValidAfter, ISOTIME, NULL), V(ValidUntil, ISOTIME, NULL), @@ -590,7 +590,7 @@ disk_state_put_srv_line(const sr_srv_t *srv, config_line_t *line) return; } sr_srv_encode(encoded, sizeof(encoded), srv); - tor_asprintf(&line->value, "%d %s", srv->num_reveals, encoded); + tor_asprintf(&line->value, "%" PRIu64 " %s", srv->num_reveals, encoded); } /* Reset disk state that is free allocated memory and zeroed the object. */ diff --git a/src/or/shared_random_state.h b/src/or/shared_random_state.h index d013650aa2..305797f0ff 100644 --- a/src/or/shared_random_state.h +++ b/src/or/shared_random_state.h @@ -36,7 +36,7 @@ typedef struct sr_state_t { /* Filename of the state file on disk. */ char *fname; /* Version of the protocol. */ - uint8_t version; + uint32_t version; /* The valid-after of the voting period we have prepared the state for. */ time_t valid_after; /* Until when is this state valid? */ @@ -76,7 +76,7 @@ typedef struct sr_state_t { typedef struct sr_disk_state_t { uint32_t magic_; /* Version of the protocol. */ - int Version; + uint32_t Version; /* Version of our running tor. */ char *TorVersion; /* Creation time of this state */ diff --git a/src/test/test_shared_random.c b/src/test/test_shared_random.c index dcd71e01aa..89400453d4 100644 --- a/src/test/test_shared_random.c +++ b/src/test/test_shared_random.c @@ -785,9 +785,6 @@ test_sr_compute_srv(void *arg) #define SRV_TEST_VECTOR \ "2A9B1D6237DAB312A40F575DA85C147663E7ED3F80E9555395F15B515C74253D" - MOCK(trusteddirserver_get_by_v3_auth_digest, - trusteddirserver_get_by_v3_auth_digest_m); - MOCK(trusteddirserver_get_by_v3_auth_digest, trusteddirserver_get_by_v3_auth_digest_m); From 09ecbdd8eef02228764018250b00ae8d8b64bb97 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Thu, 26 May 2016 12:30:00 -0400 Subject: [PATCH 23/26] prop250: Fix format string encoding in log statement Commit and reveal length macro changed from int to unsigned long int (size_t) because of the sizeof(). Signed-off-by: David Goulet --- src/or/shared_random.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/or/shared_random.c b/src/or/shared_random.c index 5f61170d5e..79151255fa 100644 --- a/src/or/shared_random.c +++ b/src/or/shared_random.c @@ -248,7 +248,7 @@ commit_decode(const char *encoded, sr_commit_t *commit) if (decoded_len != SR_COMMIT_LEN) { log_warn(LD_BUG, "SR: Commit from authority %s decoded length doesn't " - "match the expected length (%d vs %d).", + "match the expected length (%d vs %lu).", sr_commit_get_rsa_fpr(commit), decoded_len, SR_COMMIT_LEN); goto error; } @@ -301,7 +301,7 @@ reveal_decode(const char *encoded, sr_commit_t *commit) if (decoded_len != SR_REVEAL_LEN) { log_warn(LD_BUG, "SR: Reveal from authority %s decoded length is " - "doesn't match the expected length (%d vs %d)", + "doesn't match the expected length (%d vs %lu)", sr_commit_get_rsa_fpr(commit), decoded_len, SR_REVEAL_LEN); goto error; } From 6927467bef1b5c46b85d30ac77b364ed407f6d72 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 9 May 2016 20:10:43 -0400 Subject: [PATCH 24/26] Refactor parameter computation and add a helper function This patch makes us retain the intermediate list of K=V entries for the duration of computing our vote, and lets us use that list with a new function in order to look up parameters before the consensus is published. We can't actually use this function yet because of #19011: our existing code to do this doesn't actually work, and we'll need a new consensus method to start using it. Closes ticket #19012. --- src/or/dirvote.c | 60 ++++++++++++++++++++++++++++++++++----------- src/or/dirvote.h | 6 ++++- src/test/test_dir.c | 14 +++++++++++ 3 files changed, 65 insertions(+), 15 deletions(-) diff --git a/src/or/dirvote.c b/src/or/dirvote.c index 50713318bd..4ebea3262c 100644 --- a/src/or/dirvote.c +++ b/src/or/dirvote.c @@ -618,15 +618,47 @@ compute_consensus_versions_list(smartlist_t *lst, int n_versioning) return result; } +/** Given a list of K=V values, return the int32_t value corresponding to + * KEYWORD=, or default_val if no such value exists, or if the value is + * corrupt. + */ +STATIC int32_t +dirvote_get_intermediate_param_value(const smartlist_t *param_list, + const char *keyword, + int32_t default_val) +{ + unsigned int n_found = 0; + int32_t value = default_val; + + SMARTLIST_FOREACH_BEGIN(param_list, const char *, k_v_pair) { + if (!strcmpstart(k_v_pair, keyword) && k_v_pair[strlen(keyword)] == '=') { + const char *integer_str = &k_v_pair[strlen(keyword)+1]; + int ok; + value = (int32_t) + tor_parse_long(integer_str, 10, INT32_MIN, INT32_MAX, &ok, NULL); + if (BUG(! ok)) + return default_val; + ++n_found; + } + } SMARTLIST_FOREACH_END(k_v_pair); + + if (n_found == 1) + return value; + else if (BUG(n_found > 1)) + return default_val; + else + return default_val; +} + /** Minimum number of directory authorities voting for a parameter to * include it in the consensus, if consensus method 12 or later is to be * used. See proposal 178 for details. */ #define MIN_VOTES_FOR_PARAM 3 -/** Helper: given a list of valid networkstatus_t, return a new string +/** Helper: given a list of valid networkstatus_t, return a new smartlist * containing the contents of the consensus network parameter set. */ -STATIC char * +STATIC smartlist_t * dirvote_compute_params(smartlist_t *votes, int method, int total_authorities) { int i; @@ -635,7 +667,6 @@ dirvote_compute_params(smartlist_t *votes, int method, int total_authorities) int cur_param_len; const char *cur_param; const char *eq; - char *result; const int n_votes = smartlist_len(votes); smartlist_t *output; @@ -657,8 +688,7 @@ dirvote_compute_params(smartlist_t *votes, int method, int total_authorities) if (smartlist_len(param_list) == 0) { tor_free(vals); - smartlist_free(param_list); - return NULL; + return param_list; } smartlist_sort_strings(param_list); @@ -706,12 +736,9 @@ dirvote_compute_params(smartlist_t *votes, int method, int total_authorities) } } SMARTLIST_FOREACH_END(param); - result = smartlist_join_strings(output, " ", 0, NULL); - SMARTLIST_FOREACH(output, char *, cp, tor_free(cp)); - smartlist_free(output); smartlist_free(param_list); tor_free(vals); - return result; + return output; } #define RANGE_CHECK(a,b,c,d,e,f,g,mx) \ @@ -1158,6 +1185,8 @@ networkstatus_compute_consensus(smartlist_t *votes, char *packages = NULL; int added_weights = 0; dircollator_t *collator = NULL; + smartlist_t *param_list = NULL; + tor_assert(flavor == FLAV_NS || flavor == FLAV_MICRODESC); tor_assert(total_authorities >= smartlist_len(votes)); tor_assert(total_authorities > 0); @@ -1302,9 +1331,10 @@ networkstatus_compute_consensus(smartlist_t *votes, tor_free(flaglist); } - params = dirvote_compute_params(votes, consensus_method, - total_authorities); - if (params) { + param_list = dirvote_compute_params(votes, consensus_method, + total_authorities); + if (smartlist_len(param_list)) { + params = smartlist_join_strings(param_list, " ", 0, NULL); smartlist_add(chunks, tor_strdup("params ")); smartlist_add(chunks, params); smartlist_add(chunks, tor_strdup("\n")); @@ -1369,7 +1399,7 @@ networkstatus_compute_consensus(smartlist_t *votes, if (consensus_method >= MIN_METHOD_TO_CLIP_UNMEASURED_BW) { char *max_unmeasured_param = NULL; - /* XXXX Extract this code into a common function */ + /* XXXX Extract this code into a common function. Or don't! see #19011 */ if (params) { if (strcmpstart(params, "maxunmeasuredbw=") == 0) max_unmeasured_param = params; @@ -1924,7 +1954,7 @@ networkstatus_compute_consensus(smartlist_t *votes, // Parse params, extract BW_WEIGHT_SCALE if present // DO NOT use consensus_param_bw_weight_scale() in this code! // The consensus is not formed yet! - /* XXXX Extract this code into a common function */ + /* XXXX Extract this code into a common function. Or not: #19011. */ if (params) { if (strcmpstart(params, "bwweightscale=") == 0) bw_weight_param = params; @@ -2044,6 +2074,8 @@ networkstatus_compute_consensus(smartlist_t *votes, smartlist_free(flags); SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp)); smartlist_free(chunks); + SMARTLIST_FOREACH(param_list, char *, cp, tor_free(cp)); + smartlist_free(param_list); return result; } diff --git a/src/or/dirvote.h b/src/or/dirvote.h index 4c563aa5b2..2a83802307 100644 --- a/src/or/dirvote.h +++ b/src/or/dirvote.h @@ -210,9 +210,13 @@ document_signature_t *voter_get_sig_by_algorithm( digest_algorithm_t alg); #ifdef DIRVOTE_PRIVATE +STATIC int32_t dirvote_get_intermediate_param_value( + const smartlist_t *param_list, + const char *keyword, + int32_t default_val); STATIC char *format_networkstatus_vote(crypto_pk_t *private_key, networkstatus_t *v3_ns); -STATIC char *dirvote_compute_params(smartlist_t *votes, int method, +STATIC smartlist_t *dirvote_compute_params(smartlist_t *votes, int method, int total_authorities); STATIC char *compute_consensus_package_lines(smartlist_t *votes); STATIC char *make_consensus_method_list(int low, int high, const char *sep); diff --git a/src/test/test_dir.c b/src/test/test_dir.c index b0ae2d99c8..1f1b3002b6 100644 --- a/src/test/test_dir.c +++ b/src/test/test_dir.c @@ -1436,6 +1436,19 @@ test_dir_measured_bw_kb_cache(void *arg) return; } +static char * +my_dirvote_compute_params(smartlist_t *votes, int method, int total_authorities) +{ + smartlist_t *s = dirvote_compute_params(votes, method, total_authorities); + tor_assert(s); + char *res = smartlist_join_strings(s, " ", 0, NULL); + SMARTLIST_FOREACH(s, char *, cp, tor_free(cp)); + smartlist_free(s); + return res; +} + +#define dirvote_compute_params my_dirvote_compute_params + static void test_dir_param_voting(void *arg) { @@ -4298,6 +4311,7 @@ struct testcase_t dir_tests[] = { DIR_LEGACY(measured_bw_kb), DIR_LEGACY(measured_bw_kb_cache), DIR_LEGACY(param_voting), + DIR(param_voting_lookup, 0), DIR_LEGACY(v3_networkstatus), DIR(random_weighted, 0), DIR(scale_bw, 0), From 4a1904c12665b98c356aba8e7b73a3f3fd508a5b Mon Sep 17 00:00:00 2001 From: David Goulet Date: Thu, 26 May 2016 15:26:09 -0400 Subject: [PATCH 25/26] prop250: Use the new dirvote_get_intermediate_param_value for AuthDirNumSRVAgreements Signed-off-by: David Goulet --- src/or/dirvote.c | 10 +++++++- src/or/shared_random.c | 48 ++++++++++++++++++++++------------- src/or/shared_random.h | 9 ++++++- src/test/test_shared_random.c | 34 +++---------------------- 4 files changed, 50 insertions(+), 51 deletions(-) diff --git a/src/or/dirvote.c b/src/or/dirvote.c index 4ebea3262c..4fbd6b339f 100644 --- a/src/or/dirvote.c +++ b/src/or/dirvote.c @@ -1341,8 +1341,16 @@ networkstatus_compute_consensus(smartlist_t *votes, } if (consensus_method >= MIN_METHOD_FOR_SHARED_RANDOM) { + int num_dirauth = get_n_authorities(V3_DIRINFO); + /* Default value of this is 2/3 of the total number of authorities. For + * instance, if we have 9 dirauth, the default value is 6. The following + * calculation will round it down. */ + int32_t num_srv_agreements = + dirvote_get_intermediate_param_value(param_list, + "AuthDirNumSRVAgreements", + (num_dirauth * 2) / 3); /* Add the shared random value. */ - char *srv_lines = sr_get_string_for_consensus(votes); + char *srv_lines = sr_get_string_for_consensus(votes, num_srv_agreements); if (srv_lines != NULL) { smartlist_add(chunks, srv_lines); } diff --git a/src/or/shared_random.c b/src/or/shared_random.c index 79151255fa..5cdf3d0287 100644 --- a/src/or/shared_random.c +++ b/src/or/shared_random.c @@ -105,6 +105,12 @@ static const char current_srv_str[] = "shared-rand-current-value"; static const char commit_ns_str[] = "shared-rand-commit"; static const char sr_flag_ns_str[] = "shared-rand-participate"; +/* The value of the consensus param AuthDirNumSRVAgreements found in the + * vote. This is set once the consensus creation subsystem requests the + * SRV(s) that should be put in the consensus. We use this value to decide + * if we keep or not an SRV. */ +static int32_t num_srv_agreements_from_vote; + /* Return a heap allocated copy of the SRV orig. */ STATIC sr_srv_t * srv_dup(const sr_srv_t *orig) @@ -737,18 +743,6 @@ save_commit_to_state(sr_commit_t *commit) } } -/* Return the number of required participants of the SR protocol. This is based - * on a consensus params. */ -static int -get_n_voters_for_srv_agreement(void) -{ - int num_dirauths = get_n_authorities(V3_DIRINFO); - /* If the params is not found, default value should always be the maximum - * number of trusted authorities. Let's not take any chances. */ - return networkstatus_get_param(NULL, "AuthDirNumSRVAgreements", - num_dirauths, 1, num_dirauths); -} - /* Return 1 if we should we keep an SRV voted by n_agreements auths. * Return 0 if we should ignore it. */ static int @@ -769,11 +763,9 @@ should_keep_srv(int n_agreements) * to keep it. */ if (sr_state_srv_is_fresh()) { /* Check if we have super majority for this new SRV value. */ - int num_required_agreements = get_n_voters_for_srv_agreement(); - - if (n_agreements < num_required_agreements) { + if (n_agreements < num_srv_agreements_from_vote) { log_notice(LD_DIR, "SR: New SRV didn't reach agreement [%d/%d]!", - n_agreements, num_required_agreements); + n_agreements, num_srv_agreements_from_vote); return 0; } } @@ -1253,9 +1245,14 @@ sr_get_string_for_vote(void) * * This is called when a consensus (any flavor) is bring created thus it * should NEVER change the state nor the state should be changed in between - * consensus creation. */ + * consensus creation. + * + * num_srv_agreements is taken from the votes thus the voted value + * that should be used. + * */ char * -sr_get_string_for_consensus(const smartlist_t *votes) +sr_get_string_for_consensus(const smartlist_t *votes, + int32_t num_srv_agreements) { char *srv_str; const or_options_t *options = get_options(); @@ -1269,6 +1266,9 @@ sr_get_string_for_consensus(const smartlist_t *votes) goto end; } + /* Set the global value of AuthDirNumSRVAgreements found in the votes. */ + num_srv_agreements_from_vote = num_srv_agreements; + /* Check the votes and figure out if SRVs should be included in the final * consensus. */ sr_srv_t *prev_srv = get_majority_srv_from_votes(votes, 0); @@ -1340,3 +1340,15 @@ sr_save_and_cleanup(void) sr_state_save(); sr_cleanup(); } + +#ifdef TOR_UNIT_TESTS + +/* Set the global value of number of SRV agreements so the test can play + * along by calling specific functions that don't parse the votes prior for + * the AuthDirNumSRVAgreements value. */ +void set_num_srv_agreements(int32_t value) +{ + num_srv_agreements_from_vote = value; +} + +#endif /* TOR_UNIT_TESTS */ diff --git a/src/or/shared_random.h b/src/or/shared_random.h index 6d68ad79bc..3922f33e34 100644 --- a/src/or/shared_random.h +++ b/src/or/shared_random.h @@ -112,7 +112,8 @@ void sr_handle_received_commits(smartlist_t *commits, sr_commit_t *sr_parse_commit(const smartlist_t *args); sr_srv_t *sr_parse_srv(const smartlist_t *args); char *sr_get_string_for_vote(void); -char *sr_get_string_for_consensus(const smartlist_t *votes); +char *sr_get_string_for_consensus(const smartlist_t *votes, + int32_t num_srv_agreements); void sr_commit_free(sr_commit_t *commit); void sr_srv_encode(char *dst, size_t dst_len, const sr_srv_t *srv); @@ -156,4 +157,10 @@ STATIC void save_commit_during_reveal_phase(const sr_commit_t *commit); #endif /* SHARED_RANDOM_PRIVATE */ +#ifdef TOR_UNIT_TESTS + +void set_num_srv_agreements(int32_t value); + +#endif /* TOR_UNIT_TESTS */ + #endif /* TOR_SHARED_RANDOM_H */ diff --git a/src/test/test_shared_random.c b/src/test/test_shared_random.c index 89400453d4..b9003502b1 100644 --- a/src/test/test_shared_random.c +++ b/src/test/test_shared_random.c @@ -830,14 +830,6 @@ get_test_vote_with_curr_srv(const char *srv) return vote; } -/* Mock function to return the value located in the options instead of the - * consensus so we can modify it at will. */ -static networkstatus_t * -mock_networkstatus_get_latest_consensus(void) -{ - return mock_consensus; -} - /* Test the function that picks the right SRV given a bunch of votes. Make sure * that the function returns an SRV iff the majority/agreement requirements are * met. */ @@ -894,31 +886,13 @@ test_sr_get_majority_srv_from_votes(void *arg) /* Now we achieve majority for SRV_1, but not the AuthDirNumSRVAgreements requirement. So still not picking an SRV. */ + set_num_srv_agreements(8); chosen_srv = get_majority_srv_from_votes(votes, 1); tt_assert(!chosen_srv); /* We will now lower the AuthDirNumSRVAgreements requirement by tweaking the - * consensus parameter and we will try again. This time it should work. */ - { - char *my_net_params; - /* Set a dummy consensus parameter in every vote */ - SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, vote) { - vote->net_params = smartlist_new(); - smartlist_split_string(vote->net_params, - "AuthDirNumSRVAgreements=7", NULL, 0, 0); - } SMARTLIST_FOREACH_END(vote); - - /* Pretend you are making a consensus out of the votes */ - mock_consensus = tor_malloc_zero(sizeof(networkstatus_t)); - mock_consensus->net_params = smartlist_new(); - my_net_params = dirvote_compute_params(votes, 66, smartlist_len(votes)); - smartlist_split_string(mock_consensus->net_params, my_net_params, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - } - - /* Use our fake consensus for finding consensus parameters. */ - MOCK(networkstatus_get_latest_consensus, - mock_networkstatus_get_latest_consensus); + * consensus parameter and we will try again. This time it should work. */ + set_num_srv_agreements(7); chosen_srv = get_majority_srv_from_votes(votes, 1); tt_assert(chosen_srv); tt_int_op(chosen_srv->num_reveals, ==, 42); @@ -928,8 +902,6 @@ test_sr_get_majority_srv_from_votes(void *arg) SMARTLIST_FOREACH(votes, networkstatus_t *, vote, networkstatus_vote_free(vote)); smartlist_free(votes); - networkstatus_vote_free(mock_consensus); - UNMOCK(networkstatus_get_latest_consensus); } static void From 36e201dffc5d57d92113e1e68ad4b6a0a7b3471e Mon Sep 17 00:00:00 2001 From: David Goulet Date: Wed, 29 Jun 2016 15:32:51 -0400 Subject: [PATCH 26/26] prop250: Add a DEL state action and return const SRVs The *get* state query functions for the SRVs now only return const pointers and the DEL action needs to be used to delete the SRVs from the state. Signed-off-by: David Goulet --- src/or/shared_random.c | 2 +- src/or/shared_random_state.c | 91 ++++++++++++++++++++++++----------- src/or/shared_random_state.h | 9 ++-- src/test/test_shared_random.c | 6 +-- 4 files changed, 73 insertions(+), 35 deletions(-) diff --git a/src/or/shared_random.c b/src/or/shared_random.c index 5cdf3d0287..7a0273d495 100644 --- a/src/or/shared_random.c +++ b/src/or/shared_random.c @@ -589,7 +589,7 @@ STATIC int should_keep_commit(const sr_commit_t *commit, const char *voter_key, sr_phase_t phase) { - sr_commit_t *saved_commit; + const sr_commit_t *saved_commit; tor_assert(commit); tor_assert(voter_key); diff --git a/src/or/shared_random_state.c b/src/or/shared_random_state.c index eeffb1479e..d0cd460756 100644 --- a/src/or/shared_random_state.c +++ b/src/or/shared_random_state.c @@ -793,20 +793,6 @@ reset_state_for_new_protocol_run(time_t valid_after) sr_state_delete_commits(); } -/* Rotate SRV value by freeing the previous value, assigning the current - * value to the previous one and nullifying the current one. */ -STATIC void -state_rotate_srv(void) -{ - /* Get a pointer to the previous SRV so we can free it after rotation. */ - sr_srv_t *previous_srv = sr_state_get_previous_srv(); - /* Set previous SRV with the current one. */ - sr_state_set_previous_srv(sr_state_get_current_srv()); - /* Nullify the current srv. */ - sr_state_set_current_srv(NULL); - tor_free(previous_srv); -} - /* This is the first round of the new protocol run starting at * valid_after. Do the necessary housekeeping. */ STATIC void @@ -857,6 +843,7 @@ state_query_get_commit(const char *rsa_fpr) tor_assert(rsa_fpr); return digestmap_get(sr_state->commits, rsa_fpr); } + /* Helper function: This handles the GET state action using an * obj_type and data needed for the action. */ static void * @@ -921,7 +908,7 @@ state_query_put_(sr_state_object_t obj_type, void *data) } } -/* Helper function: This handles the DEL state action using an +/* Helper function: This handles the DEL_ALL state action using an * obj_type and data needed for the action. */ static void state_query_del_all_(sr_state_object_t obj_type) @@ -947,6 +934,29 @@ state_query_del_all_(sr_state_object_t obj_type) } } +/* Helper function: This handles the DEL state action using an + * obj_type and data needed for the action. */ +static void +state_query_del_(sr_state_object_t obj_type, void *data) +{ + (void) data; + + switch (obj_type) { + case SR_STATE_OBJ_PREVSRV: + tor_free(sr_state->previous_srv); + break; + case SR_STATE_OBJ_CURSRV: + tor_free(sr_state->current_srv); + break; + case SR_STATE_OBJ_COMMIT: + case SR_STATE_OBJ_COMMITS: + case SR_STATE_OBJ_PHASE: + case SR_STATE_OBJ_VALID_AFTER: + default: + tor_assert(0); + } +} + /* Query state using an action for an object type obj_type. * The data pointer needs to point to an object that the action needs * to use and if anything is required to be returned, it is stored in @@ -969,6 +979,9 @@ state_query(sr_state_action_t action, sr_state_object_t obj_type, case SR_STATE_ACTION_PUT: state_query_put_(obj_type, data); break; + case SR_STATE_ACTION_DEL: + state_query_del_(obj_type, data); + break; case SR_STATE_ACTION_DEL_ALL: state_query_del_all_(obj_type); break; @@ -986,6 +999,35 @@ state_query(sr_state_action_t action, sr_state_object_t obj_type, } } +/* Delete the current SRV value from the state freeing it and the value is set + * to NULL meaning empty. */ +static void +state_del_current_srv(void) +{ + state_query(SR_STATE_ACTION_DEL, SR_STATE_OBJ_CURSRV, NULL, NULL); +} + +/* Delete the previous SRV value from the state freeing it and the value is + * set to NULL meaning empty. */ +static void +state_del_previous_srv(void) +{ + state_query(SR_STATE_ACTION_DEL, SR_STATE_OBJ_PREVSRV, NULL, NULL); +} + +/* Rotate SRV value by freeing the previous value, assigning the current + * value to the previous one and nullifying the current one. */ +STATIC void +state_rotate_srv(void) +{ + /* First delete previous SRV from the state. Object will be freed. */ + state_del_previous_srv(); + /* Set previous SRV with the current one. */ + sr_state_set_previous_srv(sr_state_get_current_srv()); + /* Nullify the current srv. */ + sr_state_set_current_srv(NULL); +} + /* Set valid after time in the our state. */ void sr_state_set_valid_after(time_t valid_after) @@ -1004,10 +1046,10 @@ sr_state_get_phase(void) } /* Return the previous SRV value from our state. Value CAN be NULL. */ -sr_srv_t * +const sr_srv_t * sr_state_get_previous_srv(void) { - sr_srv_t *srv; + const sr_srv_t *srv; state_query(SR_STATE_ACTION_GET, SR_STATE_OBJ_PREVSRV, NULL, (void *) &srv); return srv; @@ -1023,10 +1065,10 @@ sr_state_set_previous_srv(const sr_srv_t *srv) } /* Return the current SRV value from our state. Value CAN be NULL. */ -sr_srv_t * +const sr_srv_t * sr_state_get_current_srv(void) { - sr_srv_t *srv; + const sr_srv_t *srv; state_query(SR_STATE_ACTION_GET, SR_STATE_OBJ_CURSRV, NULL, (void *) &srv); return srv; @@ -1045,14 +1087,9 @@ sr_state_set_current_srv(const sr_srv_t *srv) void sr_state_clean_srvs(void) { - sr_srv_t *previous_srv = sr_state_get_previous_srv(); - sr_srv_t *current_srv = sr_state_get_current_srv(); - - tor_free(previous_srv); - sr_state_set_previous_srv(NULL); - - tor_free(current_srv); - sr_state_set_current_srv(NULL); + /* Remove SRVs from state. They will be set to NULL as "empty". */ + state_del_previous_srv(); + state_del_current_srv(); } /* Return a pointer to the commits map from our state. CANNOT be NULL. */ diff --git a/src/or/shared_random_state.h b/src/or/shared_random_state.h index 305797f0ff..e0b6e461ba 100644 --- a/src/or/shared_random_state.h +++ b/src/or/shared_random_state.h @@ -10,8 +10,9 @@ typedef enum { SR_STATE_ACTION_GET = 1, SR_STATE_ACTION_PUT = 2, - SR_STATE_ACTION_DEL_ALL = 3, - SR_STATE_ACTION_SAVE = 4, + SR_STATE_ACTION_DEL = 3, + SR_STATE_ACTION_DEL_ALL = 4, + SR_STATE_ACTION_SAVE = 5, } sr_state_action_t; /* Object in the state that can be queried through the state API. */ @@ -101,8 +102,8 @@ void sr_state_update(time_t valid_after); void sr_state_set_valid_after(time_t valid_after); sr_phase_t sr_state_get_phase(void); -sr_srv_t *sr_state_get_previous_srv(void); -sr_srv_t *sr_state_get_current_srv(void); +const sr_srv_t *sr_state_get_previous_srv(void); +const sr_srv_t *sr_state_get_current_srv(void); void sr_state_set_previous_srv(const sr_srv_t *srv); void sr_state_set_current_srv(const sr_srv_t *srv); void sr_state_clean_srvs(void); diff --git a/src/test/test_shared_random.c b/src/test/test_shared_random.c index b9003502b1..20ed0871d7 100644 --- a/src/test/test_shared_random.c +++ b/src/test/test_shared_random.c @@ -780,7 +780,7 @@ static void test_sr_compute_srv(void *arg) { (void) arg; - sr_srv_t *current_srv = NULL; + const sr_srv_t *current_srv = NULL; #define SRV_TEST_VECTOR \ "2A9B1D6237DAB312A40F575DA85C147663E7ED3F80E9555395F15B515C74253D" @@ -1026,7 +1026,7 @@ test_state_transition(void *arg) /* Test SRV rotation in our state. */ { - sr_srv_t *cur, *prev; + const sr_srv_t *cur, *prev; test_sr_setup_srv(1); cur = sr_state_get_current_srv(); tt_assert(cur); @@ -1039,7 +1039,7 @@ test_state_transition(void *arg) /* New protocol run. */ { - sr_srv_t *cur; + const sr_srv_t *cur; /* Setup some new SRVs so we can confirm that a new protocol run * actually makes them rotate and compute new ones. */ test_sr_setup_srv(1);