commit b3b4ffce2e23bfb6a2af374cd8bfa5981628a342 Author: David Goulet dgoulet@torproject.org Date: Tue May 3 10:18:45 2016 -0400
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 dgoulet@torproject.org Signed-off-by: George Kadianakis desnacked@riseup.net --- src/or/dirvote.c | 90 ++-- 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, 1699 insertions(+), 39 deletions(-)
diff --git a/src/or/dirvote.c b/src/or/dirvote.c index ad0696e..a08f6ec 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; - - /* 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}; +/* Using the time <b>now</b>, 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); + + 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 <b>now</b>. */ diff --git a/src/or/dirvote.h b/src/or/dirvote.h index 0b1d284..f2080a5 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 19f1a7f..744a507 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 0000000..447ab27 --- /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 <b>identity</b> + * 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 <b>encoded</b> containing reveal information and + * store the information in-place in <b>commit</b>. 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 <b>args</b> 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 0000000..447de0c --- /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 0000000..9fd3b27 --- /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 <b>now</b>, 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 <b>now</b>. + * 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 <b>commit</b> to <b>state</b>. 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 <b>fname</b>, 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 + * <b>obj_type</b> and <b>data</b> 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 + * <b>obj_type</b> and <b>data</b> 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 + * <b>obj_type</b> and <b>data</b> 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 <b>action</b> for an object type <b>obj_type</b>. + * The <b>data</b> 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 + * <b>out</b>. + * + * 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 <b>identity</b>. + * 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 <b>commit</b> 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 0000000..a9a80ee --- /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 */
tor-commits@lists.torproject.org