Commit 34710574 authored by Nick Mathewson's avatar Nick Mathewson 🦀
Browse files

Implement signatures for microdesc consensuses right.

This means we need to handle the existence of multiple flavors of signature
in a detached signatures document, generate them correctly, and so on.
parent d9c71816
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -1876,6 +1876,7 @@ version_from_platform(const char *platform)
 * The format argument has three possible values:
 *   NS_V2 - Output an entry suitable for a V2 NS opinion document
 *   NS_V3_CONSENSUS - Output the first portion of a V3 NS consensus entry
 *   NS_V3_CONSENSUS_MICRODESC - DOCDOC
 *   NS_V3_VOTE - Output a complete V3 NS vote
 *   NS_CONTROL_PORT - Output a NS document for the control port
 */
+164 −54
Original line number Diff line number Diff line
@@ -11,7 +11,14 @@
 * \brief Functions to compute directory consensus, and schedule voting.
 **/

typedef struct pending_consensus_t pending_consensus_t;
/** DOCDOC*/
typedef struct pending_consensus_t {
  /** The body of the consensus that we're currently building.  Once we
   * have it built, it goes into dirserv.c */
  char *body;
  /** The parsed in-progress consensus document. */
  networkstatus_t *consensus;
} pending_consensus_t;

static int dirvote_add_signatures_to_all_pending_consensuses(
                       const char *detached_signatures_body,
@@ -433,14 +440,20 @@ compute_routerstatus_consensus(smartlist_t *votes, int consensus_method,
}

/** Given a list of strings in <b>lst</b>, set the DIGEST_LEN-byte digest at
 * <b>digest_out</b> to the hash of the concatenation of those strings. */
 * <b>digest_out</b> to the hash of the concatenation of those strings. DOCDOC
 * new arguments. */
static void
hash_list_members(char *digest_out, smartlist_t *lst)
hash_list_members(char *digest_out, size_t len_out,
                  smartlist_t *lst, digest_algorithm_t alg)
{
  crypto_digest_env_t *d = crypto_new_digest_env();
  crypto_digest_env_t *d;
  if (alg == DIGEST_SHA1)
    d = crypto_new_digest_env();
  else
    d = crypto_new_digest256_env(alg);
  SMARTLIST_FOREACH(lst, const char *, cp,
                    crypto_digest_add_bytes(d, cp, strlen(cp)));
  crypto_digest_get_digest(d, digest_out, DIGEST_LEN);
  crypto_digest_get_digest(d, digest_out, len_out);
  crypto_free_digest_env(d);
}

@@ -655,12 +668,15 @@ networkstatus_compute_consensus(smartlist_t *votes,
  int vote_seconds, dist_seconds;
  char *client_versions = NULL, *server_versions = NULL;
  smartlist_t *flags;
  const char *flavor_name;
  const routerstatus_format_type_t rs_format =
    flavor == FLAV_NS ? NS_V3_CONSENSUS : NS_V3_CONSENSUS_MICRODESC;

  tor_assert(flavor == FLAV_NS || flavor == FLAV_MICRODESC);
  tor_assert(total_authorities >= smartlist_len(votes));

  flavor_name = networkstatus_get_flavor_name(flavor);

  if (!smartlist_len(votes)) {
    log_warn(LD_DIR, "Can't compute a consensus from no votes.");
    return NULL;
@@ -762,8 +778,12 @@ networkstatus_compute_consensus(smartlist_t *votes,
    format_iso_time(vu_buf, valid_until);
    flaglist = smartlist_join_strings(flags, " ", 0, NULL);

    smartlist_add(chunks, tor_strdup("network-status-version 3\n"
                                     "vote-status consensus\n"));
    tor_snprintf(buf, sizeof(buf), "network-status-version 3%s%s\n"
                 "vote-status consensus\n",
                 flavor == FLAV_NS ? "" : " ",
                 flavor == FLAV_NS ? "" : flavor_name);

    smartlist_add(chunks, tor_strdup(buf));

    if (consensus_method >= 2) {
      tor_snprintf(buf, sizeof(buf), "consensus-method %d\n",
@@ -1285,25 +1305,36 @@ networkstatus_compute_consensus(smartlist_t *votes,

  /* Add a signature. */
  {
    char digest[DIGEST_LEN];
    char digest[DIGEST256_LEN];
    char fingerprint[HEX_DIGEST_LEN+1];
    char signing_key_fingerprint[HEX_DIGEST_LEN+1];
    digest_algorithm_t digest_alg =
      flavor == FLAV_NS ? DIGEST_SHA1 : DIGEST_SHA256;
    size_t digest_len =
      flavor == FLAV_NS ? DIGEST_LEN : DIGEST256_LEN;
    const char *algname = crypto_digest_algorithm_get_name(digest_alg);

    char buf[4096];
    smartlist_add(chunks, tor_strdup("directory-signature "));

    /* Compute the hash of the chunks. */
    hash_list_members(digest, chunks);
    hash_list_members(digest, digest_len, chunks, digest_alg);

    /* Get the fingerprints */
    crypto_pk_get_fingerprint(identity_key, fingerprint, 0);
    crypto_pk_get_fingerprint(signing_key, signing_key_fingerprint, 0);

    /* add the junk that will go at the end of the line. */
    if (flavor == FLAV_NS) {
      tor_snprintf(buf, sizeof(buf), "%s %s\n", fingerprint,
                   signing_key_fingerprint);
    } else {
      tor_snprintf(buf, sizeof(buf), "%s %s %s\n",
                   algname, fingerprint,
                   signing_key_fingerprint);
    }
    /* And the signature. */
    if (router_append_dirobj_signature(buf, sizeof(buf), digest, DIGEST_LEN,
    if (router_append_dirobj_signature(buf, sizeof(buf), digest, digest_len,
                                       signing_key)) {
      log_warn(LD_BUG, "Couldn't sign consensus networkstatus.");
      return NULL; /* This leaks, but it should never happen. */
@@ -1316,9 +1347,15 @@ networkstatus_compute_consensus(smartlist_t *votes,
                    legacy_id_key_digest, DIGEST_LEN);
      crypto_pk_get_fingerprint(legacy_signing_key,
                                signing_key_fingerprint, 0);
      if (flavor == FLAV_NS) {
        tor_snprintf(buf, sizeof(buf), "%s %s\n", fingerprint,
                     signing_key_fingerprint);
      if (router_append_dirobj_signature(buf, sizeof(buf), digest, DIGEST_LEN,
      } else {
        tor_snprintf(buf, sizeof(buf), "%s %s %s\n",
                     algname, fingerprint,
                     signing_key_fingerprint);
      }
      if (router_append_dirobj_signature(buf, sizeof(buf), digest, digest_len,
                                         legacy_signing_key)) {
        log_warn(LD_BUG, "Couldn't sign consensus networkstatus.");
        return NULL; /* This leaks, but it should never happen. */
@@ -1488,12 +1525,21 @@ networkstatus_add_detached_signatures(networkstatus_t *target,

/** DOCDOC */
static char *
networkstatus_format_signatures(networkstatus_t *consensus)
networkstatus_format_signatures(networkstatus_t *consensus,
                                int for_detached_signatures)
{
  smartlist_t *elements;
  char buf[4096];
  char *result = NULL;
  int n_sigs = 0;
  const consensus_flavor_t flavor = consensus->flavor;
  const char *flavor_name = networkstatus_get_flavor_name(flavor);
  const char *keyword;

  if (for_detached_signatures && flavor != FLAV_NS)
    keyword = "additional-signature";
  else
    keyword = "directory-signature";

  elements = smartlist_create();

@@ -1501,14 +1547,25 @@ networkstatus_format_signatures(networkstatus_t *consensus)
    SMARTLIST_FOREACH_BEGIN(v->sigs, document_signature_t *, sig) {
      char sk[HEX_DIGEST_LEN+1];
      char id[HEX_DIGEST_LEN+1];
      if (!sig->signature || sig->bad_signature || sig->alg != DIGEST_SHA1)
      if (!sig->signature || sig->bad_signature)
        continue;
      ++n_sigs;
      base16_encode(sk, sizeof(sk), sig->signing_key_digest, DIGEST_LEN);
      base16_encode(id, sizeof(id), sig->identity_digest, DIGEST_LEN);
      if (flavor == FLAV_NS) {
        tor_snprintf(buf, sizeof(buf),
                     "%s %s %s\n-----BEGIN SIGNATURE-----\n",
                     keyword, id, sk);
      } else {
        const char *digest_name =
          crypto_digest_algorithm_get_name(sig->alg);
        tor_snprintf(buf, sizeof(buf),
                   "directory-signature %s %s\n-----BEGIN SIGNATURE-----\n",
                   id, sk);
                     "%s%s%s %s %s %s\n-----BEGIN SIGNATURE-----\n",
                     keyword,
                     for_detached_signatures ? " " : "",
                     for_detached_signatures ? flavor_name : "",
                     digest_name, id, sk);
      }
      smartlist_add(elements, tor_strdup(buf));
      base64_encode(buf, sizeof(buf), sig->signature, sig->signature_len);
      strlcat(buf, "-----END SIGNATURE-----\n", sizeof(buf));
@@ -1525,17 +1582,28 @@ networkstatus_format_signatures(networkstatus_t *consensus)
}

/** Return a newly allocated string holding the detached-signatures document
 * corresponding to the signatures on <b>consensus</b>. */
 * corresponding to the signatures on <b>consensuses</b>, which must contain
 * exactly one FLAV_NS consensus, and no more than one consensus for each
 * other flavor. */
char *
networkstatus_get_detached_signatures(networkstatus_t *consensus)
networkstatus_get_detached_signatures(smartlist_t *consensuses)
{
  smartlist_t *elements;
  char buf[4096];
  char *result = NULL;
  tor_assert(consensus);
  tor_assert(consensus->type == NS_TYPE_CONSENSUS);

  tor_assert(consensus->flavor == FLAV_NS);
  char *result = NULL, *sigs = NULL;
  networkstatus_t *consensus_ns = NULL;
  tor_assert(consensuses);

  SMARTLIST_FOREACH(consensuses, networkstatus_t *, ns, {
      tor_assert(ns);
      tor_assert(ns->type == NS_TYPE_CONSENSUS);
      if (ns && ns->flavor == FLAV_NS)
        consensus_ns = ns;
  });
  if (!consensus_ns) {
    log_warn(LD_BUG, "No NS consensus given.");
    return NULL;
  }

  elements = smartlist_create();

@@ -1544,10 +1612,11 @@ networkstatus_get_detached_signatures(networkstatus_t *consensus)
      vu_buf[ISO_TIME_LEN+1];
    char d[HEX_DIGEST_LEN+1];

    base16_encode(d, sizeof(d), consensus->digests.d[DIGEST_SHA1], DIGEST_LEN);
    format_iso_time(va_buf, consensus->valid_after);
    format_iso_time(fu_buf, consensus->fresh_until);
    format_iso_time(vu_buf, consensus->valid_until);
    base16_encode(d, sizeof(d),
                  consensus_ns->digests.d[DIGEST_SHA1], DIGEST_LEN);
    format_iso_time(va_buf, consensus_ns->valid_after);
    format_iso_time(fu_buf, consensus_ns->fresh_until);
    format_iso_time(vu_buf, consensus_ns->valid_until);

    tor_snprintf(buf, sizeof(buf),
                 "consensus-digest %s\n"
@@ -1557,12 +1626,46 @@ networkstatus_get_detached_signatures(networkstatus_t *consensus)
    smartlist_add(elements, tor_strdup(buf));
  }

  {
    char *sigs = networkstatus_format_signatures(consensus);
  /* Get all the digests for the non-FLAV_NS consensuses */
  SMARTLIST_FOREACH_BEGIN(consensuses, networkstatus_t *, ns) {
    const char *flavor_name = networkstatus_get_flavor_name(ns->flavor);
    int alg;
    if (ns->flavor == FLAV_NS)
      continue;

    /* start with SHA256; we don't include SHA1 for anything but the basic
     * consensus. */
    for (alg = DIGEST_SHA256; alg < N_DIGEST_ALGORITHMS; ++alg) {
      char d[HEX_DIGEST256_LEN+1];
      const char *alg_name =
        crypto_digest_algorithm_get_name(alg);
      if (tor_mem_is_zero(ns->digests.d[alg], DIGEST256_LEN))
        continue;
      base16_encode(d, sizeof(d), ns->digests.d[alg], DIGEST256_LEN);
      tor_snprintf(buf, sizeof(buf), "additional-digest %s %s %s\n",
                   flavor_name, alg_name, d);
      smartlist_add(elements, tor_strdup(buf));
    }
  } SMARTLIST_FOREACH_END(ns);

  /* Now get all the sigs for non-FLAV_NS consensuses */
  SMARTLIST_FOREACH_BEGIN(consensuses, networkstatus_t *, ns) {
    char *sigs;
    if (ns->flavor == FLAV_NS)
      continue;
    sigs = networkstatus_format_signatures(ns, 1);
    if (!sigs) {
      log_warn(LD_DIR, "Couldn't format signatures");
      goto err;
    }
    smartlist_add(elements, sigs);
  } SMARTLIST_FOREACH_END(ns);

  /* Now add the FLAV_NS consensus signatrures. */
  sigs = networkstatus_format_signatures(consensus_ns, 1);
  if (!sigs)
    goto err;
  smartlist_add(elements, sigs);
  }

  result = smartlist_join_strings(elements, "", 0, NULL);
 err:
@@ -1571,6 +1674,23 @@ networkstatus_get_detached_signatures(networkstatus_t *consensus)
  return result;
}

/** DOCDOC */
static char *
get_detached_signatures_from_pending_consensuses(pending_consensus_t *pending,
                                                 int n_flavors)
{
  int flav;
  char *signatures;
  smartlist_t *c = smartlist_create();
  for (flav = 0; flav < n_flavors; ++flav) {
    if (pending[flav].consensus)
      smartlist_add(c, pending[flav].consensus);
  }
  signatures = networkstatus_get_detached_signatures(c);
  smartlist_free(c);
  return signatures;
}

/** Release all storage held in <b>s</b>. */
void
ns_detached_signatures_free(ns_detached_signatures_t *s)
@@ -1812,15 +1932,6 @@ static smartlist_t *pending_vote_list = NULL;
 * build a consensus, the votes go here for the next period. */
static smartlist_t *previous_vote_list = NULL;

/** DOCDOC*/
struct pending_consensus_t {
  /** The body of the consensus that we're currently building.  Once we
   * have it built, it goes into dirserv.c */
  char *body;
  /** The parsed in-progress consensus document. */
  networkstatus_t *consensus;
};

static pending_consensus_t pending_consensuses[N_CONSENSUS_FLAVORS];

/** The detached signatures for the consensus that we're currently
@@ -2277,9 +2388,8 @@ dirvote_compute_consensuses(void)
    }
  }

  /* XXXX NMNM NM NM wrong. */
  signatures =
    networkstatus_get_detached_signatures(pending[FLAV_NS].consensus);
  signatures = get_detached_signatures_from_pending_consensuses(
       pending, N_CONSENSUS_FLAVORS);

  if (!signatures) {
    log_warn(LD_DIR, "Couldn't extract signatures.");
@@ -2364,7 +2474,7 @@ dirvote_add_signatures_to_pending_consensus(

  if (r >= 1) {
    char *new_signatures =
      networkstatus_format_signatures(pc->consensus);
      networkstatus_format_signatures(pc->consensus, 0);
    char *dst, *dst_end;
    size_t new_consensus_len;
    if (!new_signatures) {
@@ -2389,7 +2499,6 @@ dirvote_add_signatures_to_pending_consensus(
      tor_assert(v);
      networkstatus_vote_free(v);
    }
    tor_free(pending_consensus_signatures);
    *msg_out = "Signatures added";
  } else if (r == 0) {
    *msg_out = "Signatures ignored";
@@ -2414,6 +2523,7 @@ dirvote_add_signatures_to_all_pending_consensuses(
  ns_detached_signatures_t *sigs;
  tor_assert(detached_signatures_body);
  tor_assert(msg_out);
  tor_assert(pending_consensus_signatures);

  if (!(sigs = networkstatus_parse_detached_signatures(
                               detached_signatures_body, NULL))) {
@@ -2438,10 +2548,10 @@ dirvote_add_signatures_to_all_pending_consensuses(
    goto err;
  }

  /* Still not right XXXX NM NM*/
  if (pending_consensuses[FLAV_NS].consensus) {
    char *new_detached = networkstatus_get_detached_signatures(
                           pending_consensuses[FLAV_NS].consensus);
  if (n_added && pending_consensuses[FLAV_NS].consensus) {
    char *new_detached =
      get_detached_signatures_from_pending_consensuses(
                      pending_consensuses, N_CONSENSUS_FLAVORS);
    if (new_detached) {
      tor_free(pending_consensus_signatures);
      pending_consensus_signatures = new_detached;
+1 −1
Original line number Diff line number Diff line
@@ -3838,7 +3838,7 @@ char *networkstatus_compute_consensus(smartlist_t *votes,
int networkstatus_add_detached_signatures(networkstatus_t *target,
                                          ns_detached_signatures_t *sigs,
                                          const char **msg_out);
char *networkstatus_get_detached_signatures(networkstatus_t *consensus);
char *networkstatus_get_detached_signatures(smartlist_t *consensuses);
void ns_detached_signatures_free(ns_detached_signatures_t *s);

/* cert manipulation */
+18 −3
Original line number Diff line number Diff line
@@ -559,6 +559,21 @@ generate_ri_from_rs(const vote_routerstatus_t *vrs)
  return r;
}

/** Helper: get a detached signatures document for a single FLAV_NS
 * consensus. */
static char *
get_detached_sigs(networkstatus_t *ns)
{
  char *r;
  smartlist_t *sl;
  tor_assert(ns && ns->flavor == FLAV_NS);
  sl = smartlist_create();
  smartlist_add(sl,ns);
  r = networkstatus_get_detached_signatures(sl);
  smartlist_free(sl);
  return r;
}

/** Run unit tests for generating and parsing V3 consensus networkstatus
 * documents. */
static void
@@ -994,7 +1009,7 @@ test_dir_v3_networkstatus(void)
    test_memeq(&con->digests, &con3->digests, sizeof(digests_t));

    /* Extract a detached signature from con3. */
    detached_text1 = networkstatus_get_detached_signatures(con3);
    detached_text1 = get_detached_sigs(con3);
    tor_assert(detached_text1);
    /* Try to parse it. */
    dsig1 = networkstatus_parse_detached_signatures(detached_text1, NULL);
@@ -1020,10 +1035,10 @@ test_dir_v3_networkstatus(void)
    }

    /* Try adding it to con2. */
    detached_text2 = networkstatus_get_detached_signatures(con2);
    detached_text2 = get_detached_sigs(con2);
    test_eq(1, networkstatus_add_detached_signatures(con2, dsig1, &msg));
    tor_free(detached_text2);
    detached_text2 = networkstatus_get_detached_signatures(con2);
    detached_text2 = get_detached_sigs(con2);
    //printf("\n<%s>\n", detached_text2);
    dsig2 = networkstatus_parse_detached_signatures(detached_text2, NULL);
    test_assert(dsig2);