Commit b3b4ffce authored by David Goulet's avatar David Goulet 🐼
Browse files

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's avatarDavid Goulet <dgoulet@torproject.org>
Signed-off-by: default avatarGeorge Kadianakis <desnacked@riseup.net>
parent 9744a40f
Loading
Loading
Loading
Loading
+51 −39
Original line number Diff line number Diff line
@@ -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: "
    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>. */
+32 −0
Original line number Diff line number Diff line
@@ -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,
+4 −0
Original line number Diff line number Diff line
@@ -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				\

src/or/shared_random.c

0 → 100644
+286 −0
Original line number Diff line number Diff line
/* 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();
}

src/or/shared_random.h

0 → 100644
+114 −0
Original line number Diff line number Diff line
/* 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 */
Loading