[or-cvs] [tor/master] Refactor consensus signature storage for multiple digests and flavors.

Nick Mathewson nickm at seul.org
Mon Oct 19 04:48:28 UTC 2009


Author: Nick Mathewson <nickm at torproject.org>
Date: Wed, 16 Sep 2009 17:01:01 -0400
Subject: Refactor consensus signature storage for multiple digests and flavors.
Commit: 3b2fc659a8ef83feedadcda32de49db06b80af10

This patch introduces a new type called document_signature_t to represent the
signature of a consensus document.  Now, each consensus document can have up
to one document signature per voter per digest algorithm.  Also, each
detached-signatures document can have up to one signature per <voter,
algorithm, flavor>.
---
 src/common/crypto.c    |   33 +++++++
 src/common/crypto.h    |   21 ++++-
 src/or/directory.c     |   10 +-
 src/or/dirserv.c       |    7 ++-
 src/or/dirvote.c       |  211 ++++++++++++++++++++++++++++++----------------
 src/or/networkstatus.c |  159 +++++++++++++++++++++++------------
 src/or/or.h            |   78 +++++++++++------
 src/or/routerlist.c    |   74 ++++++++--------
 src/or/routerparse.c   |  222 ++++++++++++++++++++++++++++++++----------------
 src/test/test_dir.c    |   74 ++++++++++-------
 10 files changed, 586 insertions(+), 303 deletions(-)

diff --git a/src/common/crypto.c b/src/common/crypto.c
index 21c8aed..ac0e628 100644
--- a/src/common/crypto.c
+++ b/src/common/crypto.c
@@ -1448,6 +1448,39 @@ crypto_digest256(char *digest, const char *m, size_t len,
   return (SHA256((const unsigned char*)m,len,(unsigned char*)digest) == NULL);
 }
 
+/** Set the digests_t in <b>ds_out</b> to contain every digest on the
+ * <b>len</b> bytes in <b>m</b> that we know how to compute.  Return 0 on
+ * success, -1 on failure. */
+int
+crypto_digest_all(digests_t *ds_out, const char *m, size_t len)
+{
+  digest_algorithm_t i;
+  tor_assert(ds_out);
+  memset(ds_out, 0, sizeof(*ds_out));
+  if (crypto_digest(ds_out->d[DIGEST_SHA1], m, len) < 0)
+    return -1;
+  for (i = DIGEST_SHA256; i < N_DIGEST_ALGORITHMS; ++i) {
+    if (crypto_digest256(ds_out->d[i], m, len, i) < 0)
+      return -1;
+  }
+  return 0;
+}
+
+/** Return the name of an algorithm, as used in directory documents. */
+const char *
+crypto_digest_algorithm_get_name(digest_algorithm_t alg)
+{
+  switch (alg) {
+    case DIGEST_SHA1:
+      return "sha1";
+    case DIGEST_SHA256:
+      return "sha256";
+    default:
+      tor_fragile_assert();
+      return "??unknown_digest??";
+  }
+}
+
 /** Intermediate information about the digest of a stream of data. */
 struct crypto_digest_env_t {
   union {
diff --git a/src/common/crypto.h b/src/common/crypto.h
index 63ea96d..ed84680 100644
--- a/src/common/crypto.h
+++ b/src/common/crypto.h
@@ -58,9 +58,22 @@
 #define HEX_DIGEST256_LEN 64
 
 typedef enum {
-  DIGEST_SHA1,
-  DIGEST_SHA256,
+  DIGEST_SHA1 = 0,
+  DIGEST_SHA256 = 1,
 } digest_algorithm_t;
+#define  N_DIGEST_ALGORITHMS (DIGEST_SHA256+1)
+
+/** A set of all the digests we know how to compute, taken on a single
+ * string.  Any digests that are shorter than 256 bits are right-padded
+ * with 0 bits.
+ *
+ * Note that this representation wastes 12 bytes for the SHA1 case, so
+ * don't use it for anything where we need to allocate a whole bunch at
+ * once.
+ **/
+typedef struct {
+  char d[N_DIGEST_ALGORITHMS][DIGEST256_LEN];
+} digests_t;
 
 typedef struct crypto_pk_env_t crypto_pk_env_t;
 typedef struct crypto_cipher_env_t crypto_cipher_env_t;
@@ -158,10 +171,12 @@ int crypto_cipher_decrypt_with_iv(crypto_cipher_env_t *env,
                                   char *to, size_t tolen,
                                   const char *from, size_t fromlen);
 
-/* SHA-1 */
+/* SHA-1 and other digests. */
 int crypto_digest(char *digest, const char *m, size_t len);
 int crypto_digest256(char *digest, const char *m, size_t len,
                      digest_algorithm_t algorithm);
+int crypto_digest_all(digests_t *ds_out, const char *m, size_t len);
+const char *crypto_digest_algorithm_get_name(digest_algorithm_t alg);
 crypto_digest_env_t *crypto_new_digest_env(void);
 crypto_digest_env_t *crypto_new_digest256_env(digest_algorithm_t algorithm);
 void crypto_free_digest_env(crypto_digest_env_t *digest);
diff --git a/src/or/directory.c b/src/or/directory.c
index 5fe2de4..19ef635 100644
--- a/src/or/directory.c
+++ b/src/or/directory.c
@@ -2330,7 +2330,7 @@ client_likes_consensus(networkstatus_t *v, const char *want_url)
 
   dir_split_resource_into_fingerprints(want_url, want_authorities, NULL, 0, 0);
   need_at_least = smartlist_len(want_authorities)/2+1;
-  SMARTLIST_FOREACH(want_authorities, const char *, d, {
+  SMARTLIST_FOREACH_BEGIN(want_authorities, const char *, d) {
     char want_digest[DIGEST_LEN];
     size_t want_len = strlen(d)/2;
     if (want_len > DIGEST_LEN)
@@ -2341,18 +2341,18 @@ client_likes_consensus(networkstatus_t *v, const char *want_url)
       continue;
     };
 
-    SMARTLIST_FOREACH(v->voters, networkstatus_voter_info_t *, vi, {
-      if (vi->signature &&
+    SMARTLIST_FOREACH_BEGIN(v->voters, networkstatus_voter_info_t *, vi) {
+      if (smartlist_len(vi->sigs) &&
           !memcmp(vi->identity_digest, want_digest, want_len)) {
         have++;
         break;
       };
-    });
+    } SMARTLIST_FOREACH_END(vi);
 
     /* early exit, if we already have enough */
     if (have >= need_at_least)
       break;
-  });
+  } SMARTLIST_FOREACH_END(d);
 
   SMARTLIST_FOREACH(want_authorities, char *, d, tor_free(d));
   smartlist_free(want_authorities);
diff --git a/src/or/dirserv.c b/src/or/dirserv.c
index e7a58ed..326a801 100644
--- a/src/or/dirserv.c
+++ b/src/or/dirserv.c
@@ -2597,12 +2597,17 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_env_t *private_key,
   voter = tor_malloc_zero(sizeof(networkstatus_voter_info_t));
   voter->nickname = tor_strdup(options->Nickname);
   memcpy(voter->identity_digest, identity_digest, DIGEST_LEN);
+  voter->sigs = smartlist_create();
+  {
+    document_signature_t *sig = tor_malloc_zero(sizeof(document_signature_t));
+    memcpy(sig->identity_digest, identity_digest, DIGEST_LEN);
+    memcpy(sig->signing_key_digest, signing_key_digest, DIGEST_LEN);
+  }
   voter->address = hostname;
   voter->addr = addr;
   voter->dir_port = options->DirPort;
   voter->or_port = options->ORPort;
   voter->contact = tor_strdup(contact);
-  memcpy(voter->signing_key_digest, signing_key_digest, DIGEST_LEN);
   if (options->V3AuthUseLegacyKey) {
     authority_cert_t *c = get_my_v3_legacy_cert();
     if (c) {
diff --git a/src/or/dirvote.c b/src/or/dirvote.c
index d200344..1bdf4cc 100644
--- a/src/or/dirvote.c
+++ b/src/or/dirvote.c
@@ -251,6 +251,19 @@ get_voter(const networkstatus_t *vote)
   return smartlist_get(vote->voters, 0);
 }
 
+/** DOCDOC */
+document_signature_t *
+voter_get_sig_by_algorithm(const networkstatus_voter_info_t *voter,
+                           digest_algorithm_t alg)
+{
+  if (!voter->sigs)
+    return NULL;
+  SMARTLIST_FOREACH(voter->sigs, document_signature_t *, sig,
+    if (sig->alg == alg)
+      return sig);
+  return NULL;
+}
+
 /** Temporary structure used in constructing a list of dir-source entries
  * for a consensus.  One of these is generated for every vote, and one more
  * for every legacy key in each vote. */
@@ -782,8 +795,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
   /* Add the authority sections. */
   {
     smartlist_t *dir_sources = smartlist_create();
-    SMARTLIST_FOREACH(votes, networkstatus_t *, v,
-    {
+    SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, v) {
       dir_src_ent_t *e = tor_malloc_zero(sizeof(dir_src_ent_t));
       e->v = v;
       e->digest = get_voter(v)->identity_digest;
@@ -797,7 +809,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
         e_legacy->is_legacy = 1;
         smartlist_add(dir_sources, e_legacy);
       }
-    });
+    } SMARTLIST_FOREACH_END(v);
     smartlist_sort(dir_sources, _compare_dir_src_ents_by_authority_id);
 
     SMARTLIST_FOREACH(dir_sources, const dir_src_ent_t *, e,
@@ -1347,84 +1359,123 @@ networkstatus_add_detached_signatures(networkstatus_t *target,
                                       const char **msg_out)
 {
   int r = 0;
+  const char *flavor;
+  smartlist_t *siglist;
   tor_assert(sigs);
   tor_assert(target);
   tor_assert(target->type == NS_TYPE_CONSENSUS);
 
+  flavor = networkstatus_get_flavor_name(target->flavor);
+
   /* Do the times seem right? */
   if (target->valid_after != sigs->valid_after) {
+    puts("A");
     *msg_out = "Valid-After times do not match "
       "when adding detached signatures to consensus";
     return -1;
   }
   if (target->fresh_until != sigs->fresh_until) {
+    puts("B");
     *msg_out = "Fresh-until times do not match "
       "when adding detached signatures to consensus";
     return -1;
   }
   if (target->valid_until != sigs->valid_until) {
+    puts("C");
     *msg_out = "Valid-until times do not match "
       "when adding detached signatures to consensus";
     return -1;
   }
-  /* Are they the same consensus? */
-  if (memcmp(target->networkstatus_digest, sigs->networkstatus_digest,
-             DIGEST_LEN)) {
-    *msg_out = "Digest mismatch when adding detached signatures to consensus";
+  siglist = strmap_get(sigs->signatures, flavor);
+  if (!siglist) {
+    puts("D");
+    *msg_out = "No signatures for given consensus flavor";
     return -1;
   }
 
-  /* For each voter in src... */
-  SMARTLIST_FOREACH_BEGIN(sigs->signatures, networkstatus_voter_info_t *,
-                          src_voter) {
-      char voter_identity[HEX_DIGEST_LEN+1];
-      networkstatus_voter_info_t *target_voter =
-        networkstatus_get_voter_by_id(target, src_voter->identity_digest);
-      authority_cert_t *cert = NULL;
-
-      base16_encode(voter_identity, sizeof(voter_identity),
-                    src_voter->identity_digest, DIGEST_LEN);
-      log_info(LD_DIR, "Looking at signature from %s", voter_identity);
-      /* If the target doesn't know about this voter, then forget it. */
-      if (!target_voter) {
-        log_info(LD_DIR, "We do not know about %s", voter_identity);
-        continue;
+  /** Make sure all the digests we know match, and at least one matches. */
+  {
+    digests_t *digests = strmap_get(sigs->digests, flavor);
+    int n_matches = 0;
+    digest_algorithm_t alg;
+    if (!digests) {
+      puts("D");
+      *msg_out = "No digests for given consensus flavor";
+      return -1;
+    }
+    for (alg = DIGEST_SHA1; alg < N_DIGEST_ALGORITHMS; ++alg) {
+      if (!tor_mem_is_zero(digests->d[alg], DIGEST256_LEN)) {
+        if (!memcmp(target->digests.d[alg], digests->d[alg], DIGEST256_LEN)) {
+          ++n_matches;
+        } else {
+          printf("F %d\n", alg);
+          printf("%s\n", hex_str(target->digests.d[alg], DIGEST256_LEN));
+          printf("%s\n", hex_str(digests->d[alg], DIGEST256_LEN));
+          *msg_out = "Mismatched digest.";
+          return -1;
+        }
       }
+    }
+    if (!n_matches) {
+      puts("G");
+      *msg_out = "No regognized digests for given consensus flavor";
+    }
+  }
 
-      /* If the target already has a good signature from this voter, then skip
-       * this one. */
-      if (target_voter->good_signature) {
-        log_info(LD_DIR, "We already have a good signature from %s",
-                         voter_identity);
-        continue;
-      }
+  /* For each voter in src... */
+  SMARTLIST_FOREACH_BEGIN(siglist, document_signature_t *, sig) {
+    char voter_identity[HEX_DIGEST_LEN+1];
+    networkstatus_voter_info_t *target_voter =
+      networkstatus_get_voter_by_id(target, sig->identity_digest);
+    authority_cert_t *cert = NULL;
+    const char *algorithm;
+    document_signature_t *old_sig = NULL;
+
+    algorithm = crypto_digest_algorithm_get_name(sig->alg);
+
+    base16_encode(voter_identity, sizeof(voter_identity),
+                  sig->identity_digest, DIGEST_LEN);
+    log_info(LD_DIR, "Looking at signature from %s using %s", voter_identity,
+             algorithm);
+    /* If the target doesn't know about this voter, then forget it. */
+    if (!target_voter) {
+      log_info(LD_DIR, "We do not know any voter with ID %s", voter_identity);
+      continue;
+    }
 
-      /* Try checking the signature if we haven't already. */
-      if (!src_voter->good_signature && !src_voter->bad_signature) {
-        cert = authority_cert_get_by_digests(src_voter->identity_digest,
-                                             src_voter->signing_key_digest);
-        if (cert) {
-          networkstatus_check_voter_signature(target, src_voter, cert);
-        }
-      }
+    old_sig = voter_get_sig_by_algorithm(target_voter, sig->alg);
 
-      /* If this signature is good, or we don't have any signature yet,
-       * then add it. */
-      if (src_voter->good_signature || !target_voter->signature) {
-        log_info(LD_DIR, "Adding signature from %s", voter_identity);
-        ++r;
-        tor_free(target_voter->signature);
-        target_voter->signature =
-          tor_memdup(src_voter->signature, src_voter->signature_len);
-        memcpy(target_voter->signing_key_digest, src_voter->signing_key_digest,
-               DIGEST_LEN);
-        target_voter->signature_len = src_voter->signature_len;
-        target_voter->good_signature = src_voter->good_signature;
-        target_voter->bad_signature = src_voter->bad_signature;
-      } else {
-        log_info(LD_DIR, "Not adding signature from %s", voter_identity);
+    /* If the target already has a good signature from this voter, then skip
+     * this one. */
+    if (old_sig && old_sig->good_signature) {
+      log_info(LD_DIR, "We already have a good signature from %s using %s",
+               voter_identity, algorithm);
+      continue;
+    }
+
+    /* Try checking the signature if we haven't already. */
+    if (!sig->good_signature && !sig->bad_signature) {
+      cert = authority_cert_get_by_digests(sig->identity_digest,
+                                           sig->signing_key_digest);
+      if (cert)
+        networkstatus_check_document_signature(target, sig, cert);
+    }
+
+    /* If this signature is good, or we don't have any signature yet,
+     * then maybe add it. */
+    if (sig->good_signature || !old_sig || old_sig->bad_signature) {
+      log_info(LD_DIR, "Adding signature from %s with %s", voter_identity,
+               algorithm);
+      ++r;
+      if (old_sig) {
+        smartlist_remove(target_voter->sigs, old_sig);
+        document_signature_free(old_sig);
       }
-  } SMARTLIST_FOREACH_END(src_voter);
+      smartlist_add(target_voter->sigs, document_signature_dup(sig));
+    } else {
+      log_info(LD_DIR, "Not adding signature from %s", voter_identity);
+    }
+  } SMARTLIST_FOREACH_END(sig);
 
   return r;
 }
@@ -1441,6 +1492,8 @@ networkstatus_get_detached_signatures(networkstatus_t *consensus)
   tor_assert(consensus);
   tor_assert(consensus->type == NS_TYPE_CONSENSUS);
 
+  tor_assert(consensus->flavor == FLAV_NS);
+
   elements = smartlist_create();
 
   {
@@ -1448,7 +1501,7 @@ 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->networkstatus_digest, DIGEST_LEN);
+    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);
@@ -1461,26 +1514,26 @@ networkstatus_get_detached_signatures(networkstatus_t *consensus)
     smartlist_add(elements, tor_strdup(buf));
   }
 
-  SMARTLIST_FOREACH(consensus->voters, networkstatus_voter_info_t *, v,
-    {
+  SMARTLIST_FOREACH_BEGIN(consensus->voters, networkstatus_voter_info_t *, v) {
+    SMARTLIST_FOREACH_BEGIN(v->sigs, document_signature_t *, sig) {
       char sk[HEX_DIGEST_LEN+1];
       char id[HEX_DIGEST_LEN+1];
-      if (!v->signature || v->bad_signature)
+      if (!sig->signature || sig->bad_signature || sig->alg != DIGEST_SHA1)
         continue;
       ++n_sigs;
-      base16_encode(sk, sizeof(sk), v->signing_key_digest, DIGEST_LEN);
-      base16_encode(id, sizeof(id), v->identity_digest, DIGEST_LEN);
+      base16_encode(sk, sizeof(sk), sig->signing_key_digest, DIGEST_LEN);
+      base16_encode(id, sizeof(id), sig->identity_digest, DIGEST_LEN);
       tor_snprintf(buf, sizeof(buf),
                    "directory-signature %s %s\n-----BEGIN SIGNATURE-----\n",
                    id, sk);
       smartlist_add(elements, tor_strdup(buf));
-      base64_encode(buf, sizeof(buf), v->signature, v->signature_len);
+      base64_encode(buf, sizeof(buf), sig->signature, sig->signature_len);
       strlcat(buf, "-----END SIGNATURE-----\n", sizeof(buf));
       smartlist_add(elements, tor_strdup(buf));
-    });
+    } SMARTLIST_FOREACH_END(sig);
+  } SMARTLIST_FOREACH_END(v);
 
   result = smartlist_join_strings(elements, "", 0, NULL);
-
   SMARTLIST_FOREACH(elements, char *, cp, tor_free(cp));
   smartlist_free(elements);
   if (!n_sigs)
@@ -1493,13 +1546,15 @@ void
 ns_detached_signatures_free(ns_detached_signatures_t *s)
 {
   if (s->signatures) {
-    SMARTLIST_FOREACH(s->signatures, networkstatus_voter_info_t *, v,
-      {
-        tor_free(v->signature);
-        tor_free(v);
-      });
-    smartlist_free(s->signatures);
+    STRMAP_FOREACH(s->signatures, flavor, smartlist_t *, sigs) {
+      SMARTLIST_FOREACH(sigs, document_signature_t *, sig,
+                        document_signature_free(sig));
+      smartlist_free(sigs);
+    } STRMAP_FOREACH_END;
+    strmap_free(s->signatures, NULL);
+    strmap_free(s->digests, _tor_free);
   }
+
   tor_free(s);
 }
 
@@ -1936,7 +1991,13 @@ dirvote_add_vote(const char *vote_body, const char **msg_out, int *status_out)
   }
   tor_assert(smartlist_len(vote->voters) == 1);
   vi = get_voter(vote);
-  tor_assert(vi->good_signature == 1);
+  {
+    int any_sig_good = 0;
+    SMARTLIST_FOREACH(vi->sigs, document_signature_t *, sig,
+                      if (sig->good_signature)
+                        any_sig_good = 1);
+    tor_assert(any_sig_good);
+  }
   ds = trusteddirserver_get_by_v3_auth_digest(vi->identity_digest);
   if (!ds) {
     char *keys = list_v3_auth_ids();
@@ -2218,8 +2279,12 @@ dirvote_add_signatures_to_pending_consensus(
     goto err;
   }
 
-  log_info(LD_DIR, "Have %d signatures for adding to consensus.",
-                   smartlist_len(sigs->signatures));
+  {
+    smartlist_t *sig_list = strmap_get(sigs->signatures,
+                    networkstatus_get_flavor_name(pending_consensus->flavor));
+    log_info(LD_DIR, "Have %d signatures for adding to consensus.",
+             sig_list ? smartlist_len(sig_list) : 0);
+  }
   r = networkstatus_add_detached_signatures(pending_consensus,
                                             sigs, msg_out);
   log_info(LD_DIR,"Added %d signatures to consensus.", r);
@@ -2406,12 +2471,12 @@ dirvote_get_vote(const char *fp, int flags)
   } else {
     if (pending_vote_list && include_pending) {
       SMARTLIST_FOREACH(pending_vote_list, pending_vote_t *, pv,
-        if (!memcmp(pv->vote->networkstatus_digest, fp, DIGEST_LEN))
+        if (!memcmp(pv->vote->digests.d[DIGEST_SHA1], fp, DIGEST_LEN))
           return pv->vote_body);
     }
     if (previous_vote_list && include_previous) {
       SMARTLIST_FOREACH(previous_vote_list, pending_vote_t *, pv,
-        if (!memcmp(pv->vote->networkstatus_digest, fp, DIGEST_LEN))
+        if (!memcmp(pv->vote->digests.d[DIGEST_SHA1], fp, DIGEST_LEN))
           return pv->vote_body);
     }
   }
diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c
index 752cb42..6167265 100644
--- a/src/or/networkstatus.c
+++ b/src/or/networkstatus.c
@@ -279,7 +279,25 @@ networkstatus_v2_free(networkstatus_v2_t *ns)
   tor_free(ns);
 }
 
-/** Clear all storage held in <b>ns</b>. */
+/** Free all storage held in <b>sig</b> */
+void
+document_signature_free(document_signature_t *sig)
+{
+  tor_free(sig->signature);
+  tor_free(sig);
+}
+
+/** Return a newly allocated copy of <b>sig</b> */
+document_signature_t *
+document_signature_dup(const document_signature_t *sig)
+{
+  document_signature_t *r = tor_memdup(sig, sizeof(document_signature_t));
+  if (r->signature)
+    r->signature = tor_memdup(sig->signature, sig->signature_len);
+  return r;
+}
+
+/** Free all storage held in <b>ns</b>. */
 void
 networkstatus_vote_free(networkstatus_t *ns)
 {
@@ -301,14 +319,17 @@ networkstatus_vote_free(networkstatus_t *ns)
     smartlist_free(ns->supported_methods);
   }
   if (ns->voters) {
-    SMARTLIST_FOREACH(ns->voters, networkstatus_voter_info_t *, voter,
-    {
+    SMARTLIST_FOREACH_BEGIN(ns->voters, networkstatus_voter_info_t *, voter) {
       tor_free(voter->nickname);
       tor_free(voter->address);
       tor_free(voter->contact);
-      tor_free(voter->signature);
+      if (voter->sigs) {
+        SMARTLIST_FOREACH(voter->sigs, document_signature_t *, sig,
+                          document_signature_free(sig));
+        smartlist_free(voter->sigs);
+      }
       tor_free(voter);
-    });
+    } SMARTLIST_FOREACH_END(voter);
     smartlist_free(ns->voters);
   }
   if (ns->cert)
@@ -347,34 +368,38 @@ networkstatus_get_voter_by_id(networkstatus_t *vote,
   return NULL;
 }
 
-/** Check whether the signature on <b>voter</b> is correctly signed by
- * the signing key of <b>cert</b>. Return -1 if <b>cert</b> doesn't match the
+/** Check whether the signature <b>sig</b> is correctly signed with the
+ * signing key in <b>cert</b>.  Return -1 if <b>cert</b> doesn't match the
  * signing key; otherwise set the good_signature or bad_signature flag on
  * <b>voter</b>, and return 0. */
-/* (private; exposed for testing.) */
 int
-networkstatus_check_voter_signature(networkstatus_t *consensus,
-                                    networkstatus_voter_info_t *voter,
-                                    authority_cert_t *cert)
+networkstatus_check_document_signature(const networkstatus_t *consensus,
+                                       document_signature_t *sig,
+                                       const authority_cert_t *cert)
 {
-  char d[DIGEST_LEN];
+  char key_digest[DIGEST_LEN];
+  const int dlen = sig->alg == DIGEST_SHA1 ? DIGEST_LEN : DIGEST256_LEN;
   char *signed_digest;
   size_t signed_digest_len;
-  if (crypto_pk_get_digest(cert->signing_key, d)<0)
+
+  if (crypto_pk_get_digest(cert->signing_key, key_digest)<0)
     return -1;
-  if (memcmp(voter->signing_key_digest, d, DIGEST_LEN))
+  if (memcmp(sig->signing_key_digest, key_digest, DIGEST_LEN) ||
+      memcmp(sig->identity_digest, cert->cache_info.identity_digest,
+             DIGEST_LEN))
     return -1;
+
   signed_digest_len = crypto_pk_keysize(cert->signing_key);
   signed_digest = tor_malloc(signed_digest_len);
   if (crypto_pk_public_checksig(cert->signing_key,
                                 signed_digest,
-                                voter->signature,
-                                voter->signature_len) != DIGEST_LEN ||
-      memcmp(signed_digest, consensus->networkstatus_digest, DIGEST_LEN)) {
+                                sig->signature,
+                                sig->signature_len) < dlen ||
+      memcmp(signed_digest, consensus->digests.d[sig->alg], dlen)) {
     log_warn(LD_DIR, "Got a bad signature on a networkstatus vote");
-    voter->bad_signature = 1;
+    sig->bad_signature = 1;
   } else {
-    voter->good_signature = 1;
+    sig->good_signature = 1;
   }
   tor_free(signed_digest);
   return 0;
@@ -407,37 +432,52 @@ networkstatus_check_consensus_signature(networkstatus_t *consensus,
 
   tor_assert(consensus->type == NS_TYPE_CONSENSUS);
 
-  SMARTLIST_FOREACH(consensus->voters, networkstatus_voter_info_t *, voter,
-  {
-    if (!voter->good_signature && !voter->bad_signature && voter->signature) {
-      /* we can try to check the signature. */
-      int is_v3_auth = trusteddirserver_get_by_v3_auth_digest(
-                                          voter->identity_digest) != NULL;
-      authority_cert_t *cert =
-        authority_cert_get_by_digests(voter->identity_digest,
-                                      voter->signing_key_digest);
-      if (!is_v3_auth) {
-        smartlist_add(unrecognized, voter);
-        ++n_unknown;
-        continue;
-      } else if (!cert || cert->expires < now) {
-        smartlist_add(need_certs_from, voter);
-        ++n_missing_key;
-        continue;
-      }
-      if (networkstatus_check_voter_signature(consensus, voter, cert) < 0) {
-        smartlist_add(need_certs_from, voter);
-        ++n_missing_key;
-        continue;
+  SMARTLIST_FOREACH_BEGIN(consensus->voters, networkstatus_voter_info_t *,
+                          voter) {
+    int good_here = 0;
+    int bad_here = 0;
+    int missing_key_here = 0;
+    SMARTLIST_FOREACH_BEGIN(voter->sigs, document_signature_t *, sig) {
+      if (!sig->good_signature && !sig->bad_signature &&
+          sig->signature) {
+        /* we can try to check the signature. */
+        int is_v3_auth = trusteddirserver_get_by_v3_auth_digest(
+                                              sig->identity_digest) != NULL;
+        authority_cert_t *cert =
+          authority_cert_get_by_digests(sig->identity_digest,
+                                        sig->signing_key_digest);
+        tor_assert(!memcmp(sig->identity_digest, voter->identity_digest,
+                           DIGEST_LEN));
+
+        if (!is_v3_auth) {
+          smartlist_add(unrecognized, voter);
+          ++n_unknown;
+          continue;
+        } else if (!cert || cert->expires < now) {
+          smartlist_add(need_certs_from, voter);
+          ++missing_key_here;
+          continue;
+        }
+        if (networkstatus_check_document_signature(consensus, sig, cert) < 0) {
+          smartlist_add(need_certs_from, voter);
+          ++missing_key_here;
+          continue;
+        }
       }
-    }
-    if (voter->good_signature)
+      if (sig->good_signature)
+        ++good_here;
+      else if (sig->bad_signature)
+        ++bad_here;
+    } SMARTLIST_FOREACH_END(sig);
+    if (good_here)
       ++n_good;
-    else if (voter->bad_signature)
+    else if (bad_here)
       ++n_bad;
+    else if (missing_key_here)
+      ++n_missing_key;
     else
       ++n_no_signature;
-  });
+  } SMARTLIST_FOREACH_END(voter);
 
   /* Now see whether we're missing any voters entirely. */
   SMARTLIST_FOREACH(router_get_trusted_dir_servers(),
@@ -1434,8 +1474,7 @@ networkstatus_set_current_consensus(const char *consensus, unsigned flags)
   }
 
   if (current_consensus &&
-      !memcmp(c->networkstatus_digest, current_consensus->networkstatus_digest,
-              DIGEST_LEN)) {
+      !memcmp(&c->digests, &current_consensus->digests, sizeof(c->digests))) {
     /* We already have this one. That's a failure. */
     log_info(LD_DIR, "Got a consensus we already have");
     goto done;
@@ -1669,10 +1708,8 @@ download_status_map_update_from_v2_networkstatus(void)
     v2_download_status_map = digestmap_new();
 
   dl_status = digestmap_new();
-  SMARTLIST_FOREACH(networkstatus_v2_list, networkstatus_v2_t *, ns,
-  {
-    SMARTLIST_FOREACH(ns->entries, routerstatus_t *, rs,
-    {
+  SMARTLIST_FOREACH_BEGIN(networkstatus_v2_list, networkstatus_v2_t *, ns) {
+    SMARTLIST_FOREACH_BEGIN(ns->entries, routerstatus_t *, rs) {
       const char *d = rs->descriptor_digest;
       download_status_t *s;
       if (digestmap_get(dl_status, d))
@@ -1681,8 +1718,8 @@ download_status_map_update_from_v2_networkstatus(void)
         s = tor_malloc_zero(sizeof(download_status_t));
       }
       digestmap_set(dl_status, d, s);
-    });
-  });
+    } SMARTLIST_FOREACH_END(rs);
+  } SMARTLIST_FOREACH_END(ns);
   digestmap_free(v2_download_status_map, _tor_free);
   v2_download_status_map = dl_status;
   networkstatus_v2_list_has_changed = 0;
@@ -1930,6 +1967,22 @@ networkstatus_get_param(networkstatus_t *ns, const char *param_name,
   return default_val;
 }
 
+/** Return the name of the consensus flavor <b>flav</b> as used to identify
+ * the flavor in directory documents. */
+const char *
+networkstatus_get_flavor_name(consensus_flavor_t flav)
+{
+  switch (flav) {
+    case FLAV_NS:
+      return "ns";
+    case FLAV_MICRODESC:
+      return "microdesc";
+    default:
+      tor_fragile_assert();
+      return "??";
+  }
+}
+
 /** If <b>question</b> is a string beginning with "ns/" in a format the
  * control interface expects for a GETINFO question, set *<b>answer</b> to a
  * newly-allocated string containing networkstatus lines for the appropriate
diff --git a/src/or/or.h b/src/or/or.h
index 6a7aec6..e2050ec 100644
--- a/src/or/or.h
+++ b/src/or/or.h
@@ -1641,29 +1641,42 @@ typedef struct vote_routerstatus_t {
   vote_microdesc_hash_t *microdesc;
 } vote_routerstatus_t;
 
+/** A signature of some document by an authority. */
+typedef struct document_signature_t {
+  /** Declared SHA-1 digest of this voter's identity key */
+  char identity_digest[DIGEST_LEN];
+  /** Declared SHA-1 digest of signing key used by this voter. */
+  char signing_key_digest[DIGEST_LEN];
+  /** Algorithm used to compute the digest of the document. */
+  digest_algorithm_t alg;
+  /** Signature of the signed thing. */
+  char *signature;
+  /** Length of <b>signature</b> */
+  int signature_len;
+  unsigned int bad_signature : 1; /**< Set to true if we've tried to verify
+                                   * the sig, and we know it's bad. */
+  unsigned int good_signature : 1; /**< Set to true if we've verified the sig
+                                     * as good. */
+} document_signature_t;
+
 /** Information about a single voter in a vote or a consensus. */
 typedef struct networkstatus_voter_info_t {
+  /** Declared SHA-1 digest of this voter's identity key */
+  char identity_digest[DIGEST_LEN];
   char *nickname; /**< Nickname of this voter */
-  char identity_digest[DIGEST_LEN]; /**< Digest of this voter's identity key */
+  /** Digest of this voter's "legacy" identity key, if any.  In vote only; for
+   * consensuses, we treat legacy keys as additional signers. */
+  char legacy_id_digest[DIGEST_LEN];
   char *address; /**< Address of this voter, in string format. */
   uint32_t addr; /**< Address of this voter, in IPv4, in host order. */
   uint16_t dir_port; /**< Directory port of this voter */
   uint16_t or_port; /**< OR port of this voter */
   char *contact; /**< Contact information for this voter. */
   char vote_digest[DIGEST_LEN]; /**< Digest of this voter's vote, as signed. */
-  /** Digest of this voter's "legacy" identity key, if any.  In vote only; for
-   * consensuses, we treat legacy keys as additional signers. */
-  char legacy_id_digest[DIGEST_LEN];
 
   /* Nothing from here on is signed. */
-  char signing_key_digest[DIGEST_LEN]; /**< Declared digest of signing key
-                                        * used by this voter. */
-  char *signature; /**< Signature from this voter. */
-  int signature_len; /**< Length of <b>signature</b> */
-  unsigned int bad_signature : 1; /**< Set to true if we've tried to verify
-                                   * the sig, and we know it's bad. */
-  unsigned int good_signature : 1; /**< Set to true if we've verified the sig
-                                     * as good. */
+  /** The signature of the document and the signature's status. */
+  smartlist_t *sigs;
 } networkstatus_voter_info_t;
 
 /** Enumerates the possible seriousness values of a networkstatus document. */
@@ -1673,10 +1686,17 @@ typedef enum {
   NS_TYPE_OPINION,
 } networkstatus_type_t;
 
+/** DOCDOC */
+typedef enum {
+  FLAV_NS,
+  FLAV_MICRODESC,
+} consensus_flavor_t;
+
 /** A common structure to hold a v3 network status vote, or a v3 network
  * status consensus. */
 typedef struct networkstatus_t {
-  networkstatus_type_t type; /**< Vote, consensus, or opinion? */
+  networkstatus_type_t type : 8; /**< Vote, consensus, or opinion? */
+  consensus_flavor_t flavor : 8; /**< If a consensus, what kind? */
   time_t published; /**< Vote only: Time when vote was written. */
   time_t valid_after; /**< Time after which this vote or consensus applies. */
   time_t fresh_until; /**< Time before which this is the most recent vote or
@@ -1715,8 +1735,8 @@ typedef struct networkstatus_t {
 
   struct authority_cert_t *cert; /**< Vote only: the voter's certificate. */
 
-  /** Digest of this document, as signed. */
-  char networkstatus_digest[DIGEST_LEN];
+  /** Digests of this document, as signed. */
+  digests_t digests;
 
   /** List of router statuses, sorted by identity digest.  For a vote,
    * the elements are vote_routerstatus_t; for a consensus, the elements
@@ -1728,14 +1748,15 @@ typedef struct networkstatus_t {
   digestmap_t *desc_digest_map;
 } networkstatus_t;
 
-/** A set of signatures for a networkstatus consensus.  All fields are as for
- * networkstatus_t. */
+/** A set of signatures for a networkstatus consensus.  Unless otherwise
+ * noted, all fields are as for networkstatus_t. */
 typedef struct ns_detached_signatures_t {
   time_t valid_after;
   time_t fresh_until;
   time_t valid_until;
-  char networkstatus_digest[DIGEST_LEN];
-  smartlist_t *signatures; /* list of networkstatus_voter_info_t */
+  strmap_t *digests; /**< Map from flavor name to digestset_t */
+  strmap_t *signatures; /**< Map from flavor name to list of
+                         * document_signature_t */
 } ns_detached_signatures_t;
 
 /** Allowable types of desc_store_t. */
@@ -3803,12 +3824,6 @@ int dirserv_read_measured_bandwidths(const char *from_file,
 
 void dirvote_free_all(void);
 
-/** DOCDOC */
-typedef enum {
-  FLAV_NS,
-  FLAV_MICRODESC,
-} consensus_flavor_t;
-
 /* vote manipulation */
 char *networkstatus_compute_consensus(smartlist_t *votes,
                                       int total_authorities,
@@ -3869,6 +3884,9 @@ int dirvote_format_microdesc_vote_line(char *out, size_t out_len,
 int vote_routerstatus_find_microdesc_hash(char *digest256_out,
                                           const vote_routerstatus_t *vrs,
                                           int method);
+document_signature_t *voter_get_sig_by_algorithm(
+                           const networkstatus_voter_info_t *voter,
+                           digest_algorithm_t alg);
 
 #ifdef DIRVOTE_PRIVATE
 char *format_networkstatus_vote(crypto_pk_env_t *private_key,
@@ -4134,9 +4152,9 @@ networkstatus_voter_info_t *networkstatus_get_voter_by_id(
                                        const char *identity);
 int networkstatus_check_consensus_signature(networkstatus_t *consensus,
                                             int warn);
-int networkstatus_check_voter_signature(networkstatus_t *consensus,
-                                        networkstatus_voter_info_t *voter,
-                                        authority_cert_t *cert);
+int networkstatus_check_document_signature(const networkstatus_t *consensus,
+                                           document_signature_t *sig,
+                                           const authority_cert_t *cert);
 char *networkstatus_get_cache_filename(const char *identity_digest);
 int router_set_networkstatus_v2(const char *s, time_t arrived_at,
                              v2_networkstatus_source_t source,
@@ -4189,6 +4207,9 @@ int32_t networkstatus_get_param(networkstatus_t *ns, const char *param_name,
                                 int32_t default_val);
 int getinfo_helper_networkstatus(control_connection_t *conn,
                                  const char *question, char **answer);
+const char *networkstatus_get_flavor_name(consensus_flavor_t flav);
+void document_signature_free(document_signature_t *sig);
+document_signature_t *document_signature_dup(const document_signature_t *sig);
 void networkstatus_free_all(void);
 
 /********************************* ntmain.c ***************************/
@@ -4975,6 +4996,7 @@ int router_get_runningrouters_hash(const char *s, char *digest);
 int router_get_networkstatus_v2_hash(const char *s, char *digest);
 int router_get_networkstatus_v3_hash(const char *s, char *digest,
                                      digest_algorithm_t algorithm);
+int router_get_networkstatus_v3_hashes(const char *s, digests_t *digests);
 int router_get_extrainfo_hash(const char *s, char *digest);
 int router_append_dirobj_signature(char *buf, size_t buf_len,
                                    const char *digest,
diff --git a/src/or/routerlist.c b/src/or/routerlist.c
index 0a32f78..5ae40dd 100644
--- a/src/or/routerlist.c
+++ b/src/or/routerlist.c
@@ -448,17 +448,18 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now)
 
   list_pending_downloads(pending, DIR_PURPOSE_FETCH_CERTIFICATE, "fp/");
   if (status) {
-    SMARTLIST_FOREACH(status->voters, networkstatus_voter_info_t *, voter,
-      {
-        if (tor_digest_is_zero(voter->signing_key_digest))
-          continue; /* This authority never signed this consensus, so don't
-                     * go looking for a cert with key digest 0000000000. */
-        if (!cache &&
-            !trusteddirserver_get_by_v3_auth_digest(voter->identity_digest))
-          continue; /* We are not a cache, and we don't know this authority.*/
-        cl = get_cert_list(voter->identity_digest);
+    SMARTLIST_FOREACH_BEGIN(status->voters, networkstatus_voter_info_t *,
+                            voter) {
+      if (!smartlist_len(voter->sigs))
+        continue; /* This authority never signed this consensus, so don't
+                   * go looking for a cert with key digest 0000000000. */
+      if (!cache &&
+          !trusteddirserver_get_by_v3_auth_digest(voter->identity_digest))
+        continue; /* We are not a cache, and we don't know this authority.*/
+      cl = get_cert_list(voter->identity_digest);
+      SMARTLIST_FOREACH_BEGIN(voter->sigs, document_signature_t *, sig) {
         cert = authority_cert_get_by_digests(voter->identity_digest,
-                                             voter->signing_key_digest);
+                                             sig->signing_key_digest);
         if (cert) {
           if (now < cert->expires)
             download_status_reset(&cl->dl_status);
@@ -469,37 +470,36 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now)
             !digestmap_get(pending, voter->identity_digest)) {
           log_notice(LD_DIR, "We're missing a certificate from authority "
                      "with signing key %s: launching request.",
-                     hex_str(voter->signing_key_digest, DIGEST_LEN));
-          smartlist_add(missing_digests, voter->identity_digest);
+                     hex_str(sig->signing_key_digest, DIGEST_LEN));
+          smartlist_add(missing_digests, sig->identity_digest);
         }
-      });
+      } SMARTLIST_FOREACH_END(sig);
+    } SMARTLIST_FOREACH_END(voter);
   }
-  SMARTLIST_FOREACH(trusted_dir_servers, trusted_dir_server_t *, ds,
-    {
-      int found = 0;
-      if (!(ds->type & V3_AUTHORITY))
-        continue;
-      if (smartlist_digest_isin(missing_digests, ds->v3_identity_digest))
-        continue;
-      cl = get_cert_list(ds->v3_identity_digest);
-      SMARTLIST_FOREACH(cl->certs, authority_cert_t *, cert,
-        {
-          if (!ftime_definitely_after(now, cert->expires)) {
-            /* It's not expired, and we weren't looking for something to
-             * verify a consensus with.  Call it done. */
-            download_status_reset(&cl->dl_status);
-            found = 1;
-            break;
-          }
-        });
-      if (!found &&
-          download_status_is_ready(&cl->dl_status, now,MAX_CERT_DL_FAILURES) &&
-          !digestmap_get(pending, ds->v3_identity_digest)) {
-        log_notice(LD_DIR, "No current certificate known for authority %s; "
-                   "launching request.", ds->nickname);
-        smartlist_add(missing_digests, ds->v3_identity_digest);
+  SMARTLIST_FOREACH_BEGIN(trusted_dir_servers, trusted_dir_server_t *, ds) {
+    int found = 0;
+    if (!(ds->type & V3_AUTHORITY))
+      continue;
+    if (smartlist_digest_isin(missing_digests, ds->v3_identity_digest))
+      continue;
+    cl = get_cert_list(ds->v3_identity_digest);
+    SMARTLIST_FOREACH(cl->certs, authority_cert_t *, cert, {
+      if (!ftime_definitely_after(now, cert->expires)) {
+        /* It's not expired, and we weren't looking for something to
+         * verify a consensus with.  Call it done. */
+        download_status_reset(&cl->dl_status);
+        found = 1;
+        break;
       }
     });
+    if (!found &&
+        download_status_is_ready(&cl->dl_status, now,MAX_CERT_DL_FAILURES) &&
+        !digestmap_get(pending, ds->v3_identity_digest)) {
+      log_notice(LD_DIR, "No current certificate known for authority %s; "
+                 "launching request.", ds->nickname);
+        smartlist_add(missing_digests, ds->v3_identity_digest);
+    }
+  } SMARTLIST_FOREACH_END(ds);
 
   if (!smartlist_len(missing_digests)) {
     goto done;
diff --git a/src/or/routerparse.c b/src/or/routerparse.c
index 277c7c6..285a550 100644
--- a/src/or/routerparse.c
+++ b/src/or/routerparse.c
@@ -516,6 +516,9 @@ static int router_get_hash_impl(const char *s, char *digest,
                                 const char *start_str, const char *end_str,
                                 char end_char,
                                 digest_algorithm_t alg);
+static int router_get_hashes_impl(const char *s, digests_t *digests,
+                                  const char *start_str, const char *end_str,
+                                  char end_char);
 static void token_free(directory_token_t *tok);
 static smartlist_t *find_all_exitpolicy(smartlist_t *s);
 static directory_token_t *_find_by_keyword(smartlist_t *s,
@@ -633,6 +636,16 @@ router_get_networkstatus_v2_hash(const char *s, char *digest)
                               DIGEST_SHA1);
 }
 
+/** DOCDOC */
+int
+router_get_networkstatus_v3_hashes(const char *s, digests_t *digests)
+{
+  return router_get_hashes_impl(s,digests,
+                                "network-status-version",
+                                "\ndirectory-signature",
+                                ' ');
+}
+
 /** Set <b>digest</b> to the SHA-1 digest of the hash of the network-status
  * string in <b>s</b>.  Return 0 on success, -1 on failure. */
 int
@@ -640,7 +653,8 @@ router_get_networkstatus_v3_hash(const char *s, char *digest,
                                  digest_algorithm_t alg)
 {
   return router_get_hash_impl(s,digest,
-                              "network-status-version","\ndirectory-signature",
+                              "network-status-version",
+                              "\ndirectory-signature",
                               ' ', alg);
 }
 
@@ -2304,7 +2318,7 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
   smartlist_t *rs_tokens = NULL, *footer_tokens = NULL;
   networkstatus_voter_info_t *voter = NULL;
   networkstatus_t *ns = NULL;
-  char ns_digest[DIGEST_LEN];
+  digests_t ns_digests;
   const char *cert, *end_of_header, *end_of_footer, *s_dup = s;
   directory_token_t *tok;
   int ok;
@@ -2316,7 +2330,7 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
   if (eos_out)
     *eos_out = NULL;
 
-  if (router_get_networkstatus_v3_hash(s, ns_digest, DIGEST_SHA1)) {
+  if (router_get_networkstatus_v3_hashes(s, &ns_digests)) {
     log_warn(LD_DIR, "Unable to compute digest of network-status");
     goto err;
   }
@@ -2332,7 +2346,7 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
   }
 
   ns = tor_malloc_zero(sizeof(networkstatus_t));
-  memcpy(ns->networkstatus_digest, ns_digest, DIGEST_LEN);
+  memcpy(&ns->digests, &ns_digests, sizeof(ns_digests));
 
   if (ns_type != NS_TYPE_CONSENSUS) {
     const char *end_of_cert = NULL;
@@ -2486,8 +2500,9 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
       if (voter)
         smartlist_add(ns->voters, voter);
       voter = tor_malloc_zero(sizeof(networkstatus_voter_info_t));
+      voter->sigs = smartlist_create();
       if (ns->type != NS_TYPE_CONSENSUS)
-        memcpy(voter->vote_digest, ns_digest, DIGEST_LEN);
+        memcpy(voter->vote_digest, ns_digests.d[DIGEST_SHA1], DIGEST_LEN);
 
       voter->nickname = tor_strdup(tok->args[0]);
       if (strlen(tok->args[1]) != HEX_DIGEST_LEN ||
@@ -2622,10 +2637,10 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
     goto err;
   }
 
-  SMARTLIST_FOREACH(footer_tokens, directory_token_t *, _tok,
-  {
+  SMARTLIST_FOREACH_BEGIN(footer_tokens, directory_token_t *, _tok) {
     char declared_identity[DIGEST_LEN];
     networkstatus_voter_info_t *v;
+    document_signature_t *sig;
     tok = _tok;
     if (tok->tp != K_DIRECTORY_SIGNATURE)
       continue;
@@ -2650,11 +2665,15 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
                "any declared directory source.");
       goto err;
     }
+    sig = tor_malloc_zero(sizeof(document_signature_t));
+    memcpy(sig->identity_digest, v->identity_digest, DIGEST_LEN);
+    sig->alg = DIGEST_SHA1;
     if (strlen(tok->args[1]) != HEX_DIGEST_LEN ||
-        base16_decode(v->signing_key_digest, sizeof(v->signing_key_digest),
+        base16_decode(sig->signing_key_digest, sizeof(sig->signing_key_digest),
                       tok->args[1], HEX_DIGEST_LEN) < 0) {
       log_warn(LD_DIR, "Error decoding declared digest %s in "
                "network-status vote.", escaped(tok->args[1]));
+      tor_free(sig);
       goto err;
     }
 
@@ -2663,32 +2682,42 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
                  DIGEST_LEN)) {
         log_warn(LD_DIR, "Digest mismatch between declared and actual on "
                  "network-status vote.");
+        tor_free(sig);
         goto err;
       }
     }
 
+    if (voter_get_sig_by_algorithm(v, sig->alg)) {
+      /* We already parsed a vote with this algorithm from this voter. Use the
+         first one. */
+      log_fn(LOG_PROTOCOL_WARN, LD_DIR, "We received a networkstatus "
+             "that contains two votes from the same voter with the same "
+             "algorithm. Ignoring the second vote.");
+      tor_free(sig);
+      continue;
+    }
+
     if (ns->type != NS_TYPE_CONSENSUS) {
-      if (check_signature_token(ns_digest, DIGEST_LEN,
+      if (check_signature_token(ns_digests.d[DIGEST_SHA1], DIGEST_LEN,
                                 tok, ns->cert->signing_key, 0,
-                                "network-status vote"))
+                                "network-status vote")) {
+        tor_free(sig);
         goto err;
-      v->good_signature = 1;
+      }
+      sig->good_signature = 1;
     } else {
-      if (tok->object_size >= INT_MAX)
+      if (tok->object_size >= INT_MAX) {
+        tor_free(sig);
         goto err;
-      /* We already parsed a vote from this voter. Use the first one. */
-      if (v->signature) {
-        log_fn(LOG_PROTOCOL_WARN, LD_DIR, "We received a networkstatus "
-                   "that contains two votes from the same voter. Ignoring "
-                   "the second vote.");
-        continue;
       }
+      sig->signature = tor_memdup(tok->object_body, tok->object_size);
+      sig->signature_len = (int) tok->object_size;
 
-      v->signature = tor_memdup(tok->object_body, tok->object_size);
-      v->signature_len = (int) tok->object_size;
     }
+    smartlist_add(v->sigs, sig);
+
     ++n_signatures;
-  });
+  } SMARTLIST_FOREACH_END(_tok);
 
   if (! n_signatures) {
     log_warn(LD_DIR, "No signatures on networkstatus vote.");
@@ -2714,10 +2743,14 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
     smartlist_free(tokens);
   }
   if (voter) {
+    if (voter->sigs) {
+      SMARTLIST_FOREACH(voter->sigs, document_signature_t *, sig,
+                        document_signature_free(sig));
+      smartlist_free(voter->sigs);
+    }
     tor_free(voter->nickname);
     tor_free(voter->address);
     tor_free(voter->contact);
-    tor_free(voter->signature);
     tor_free(voter);
   }
   if (rs_tokens) {
@@ -2747,10 +2780,19 @@ networkstatus_parse_detached_signatures(const char *s, const char *eos)
    * networkstatus_parse_vote_from_string(). */
   directory_token_t *tok;
   memarea_t *area = NULL;
+  const char *flavor = "ns";
+  digests_t *digests;
+  smartlist_t *sig_list;
 
   smartlist_t *tokens = smartlist_create();
   ns_detached_signatures_t *sigs =
     tor_malloc_zero(sizeof(ns_detached_signatures_t));
+  sigs->digests = strmap_new();
+  sigs->signatures = strmap_new();
+  digests = tor_malloc_zero(sizeof(digests_t));
+  sig_list = smartlist_create();
+  strmap_set(sigs->digests, flavor, digests);
+  strmap_set(sigs->signatures, flavor, sig_list);
 
   if (!eos)
     eos = s + strlen(s);
@@ -2768,7 +2810,7 @@ networkstatus_parse_detached_signatures(const char *s, const char *eos)
              "networkstatus signatures");
     goto err;
   }
-  if (base16_decode(sigs->networkstatus_digest, DIGEST_LEN,
+  if (base16_decode(digests->d[DIGEST_SHA1], DIGEST_LEN,
                     tok->args[0], strlen(tok->args[0])) < 0) {
     log_warn(LD_DIR, "Bad encoding on on consensus-digest in detached "
              "networkstatus signatures");
@@ -2793,50 +2835,51 @@ networkstatus_parse_detached_signatures(const char *s, const char *eos)
     goto err;
   }
 
-  sigs->signatures = smartlist_create();
-  SMARTLIST_FOREACH(tokens, directory_token_t *, _tok,
-    {
-      char id_digest[DIGEST_LEN];
-      char sk_digest[DIGEST_LEN];
-      networkstatus_voter_info_t *voter;
-
-      tok = _tok;
-      if (tok->tp != K_DIRECTORY_SIGNATURE)
-        continue;
-      tor_assert(tok->n_args >= 2);
-
-      if (!tok->object_type ||
-          strcmp(tok->object_type, "SIGNATURE") ||
-          tok->object_size < 128 || tok->object_size > 512) {
-        log_warn(LD_DIR, "Bad object type or length on directory-signature");
-        goto err;
-      }
+  SMARTLIST_FOREACH_BEGIN(tokens, directory_token_t *, _tok) {
+    char id_digest[DIGEST_LEN];
+    char sk_digest[DIGEST_LEN];
+    document_signature_t *sig;
 
-      if (strlen(tok->args[0]) != HEX_DIGEST_LEN ||
-          base16_decode(id_digest, sizeof(id_digest),
-                        tok->args[0], HEX_DIGEST_LEN) < 0) {
-        log_warn(LD_DIR, "Error decoding declared identity %s in "
-                 "network-status vote.", escaped(tok->args[0]));
-        goto err;
-      }
-      if (strlen(tok->args[1]) != HEX_DIGEST_LEN ||
-          base16_decode(sk_digest, sizeof(sk_digest),
-                        tok->args[1], HEX_DIGEST_LEN) < 0) {
-        log_warn(LD_DIR, "Error decoding declared digest %s in "
-                 "network-status vote.", escaped(tok->args[1]));
-        goto err;
-      }
+    tok = _tok;
+    if (tok->tp != K_DIRECTORY_SIGNATURE)
+      continue;
+    tor_assert(tok->n_args >= 2);
 
-      voter = tor_malloc_zero(sizeof(networkstatus_voter_info_t));
-      memcpy(voter->identity_digest, id_digest, DIGEST_LEN);
-      memcpy(voter->signing_key_digest, sk_digest, DIGEST_LEN);
-      if (tok->object_size >= INT_MAX)
-        goto err;
-      voter->signature = tor_memdup(tok->object_body, tok->object_size);
-      voter->signature_len = (int) tok->object_size;
+    if (!tok->object_type ||
+        strcmp(tok->object_type, "SIGNATURE") ||
+        tok->object_size < 128 || tok->object_size > 512) {
+      log_warn(LD_DIR, "Bad object type or length on directory-signature");
+      goto err;
+    }
+
+    if (strlen(tok->args[0]) != HEX_DIGEST_LEN ||
+        base16_decode(id_digest, sizeof(id_digest),
+                      tok->args[0], HEX_DIGEST_LEN) < 0) {
+      log_warn(LD_DIR, "Error decoding declared identity %s in "
+               "network-status vote.", escaped(tok->args[0]));
+      goto err;
+    }
+    if (strlen(tok->args[1]) != HEX_DIGEST_LEN ||
+        base16_decode(sk_digest, sizeof(sk_digest),
+                      tok->args[1], HEX_DIGEST_LEN) < 0) {
+      log_warn(LD_DIR, "Error decoding declared digest %s in "
+               "network-status vote.", escaped(tok->args[1]));
+      goto err;
+    }
+
+    sig = tor_malloc_zero(sizeof(document_signature_t));
+    sig->alg = DIGEST_SHA1;
+    memcpy(sig->identity_digest, id_digest, DIGEST_LEN);
+    memcpy(sig->signing_key_digest, sk_digest, DIGEST_LEN);
+    if (tok->object_size >= INT_MAX) {
+      tor_free(sig);
+      goto err;
+    }
+    sig->signature = tor_memdup(tok->object_body, tok->object_size);
+    sig->signature_len = (int) tok->object_size;
 
-      smartlist_add(sigs->signatures, voter);
-    });
+    smartlist_add(sig_list, sig);
+  } SMARTLIST_FOREACH_END(_tok);
 
   goto done;
  err:
@@ -3428,18 +3471,11 @@ find_all_exitpolicy(smartlist_t *s)
   return out;
 }
 
-/** Compute the digest of the substring of <b>s</b> taken from the first
- * occurrence of <b>start_str</b> through the first instance of c after the
- * first subsequent occurrence of <b>end_str</b>; store the 20-byte result in
- * <b>digest</b>; return 0 on success.
- *
- * If no such substring exists, return -1.
- */
 static int
-router_get_hash_impl(const char *s, char *digest,
-                     const char *start_str,
-                     const char *end_str, char end_c,
-                     digest_algorithm_t alg)
+router_get_hash_impl_helper(const char *s,
+                            const char *start_str,
+                            const char *end_str, char end_c,
+                            const char **start_out, const char **end_out)
 {
   char *start, *end;
   start = strstr(s, start_str);
@@ -3465,6 +3501,28 @@ router_get_hash_impl(const char *s, char *digest,
   }
   ++end;
 
+  *start_out = start;
+  *end_out = end;
+  return 0;
+}
+
+/** Compute the digest of the substring of <b>s</b> taken from the first
+ * occurrence of <b>start_str</b> through the first instance of c after the
+ * first subsequent occurrence of <b>end_str</b>; store the 20-byte result in
+ * <b>digest</b>; return 0 on success.
+ *
+ * If no such substring exists, return -1.
+ */
+static int
+router_get_hash_impl(const char *s, char *digest,
+                     const char *start_str,
+                     const char *end_str, char end_c,
+                     digest_algorithm_t alg)
+{
+  const char *start=NULL, *end=NULL;
+  if (router_get_hash_impl_helper(s,start_str,end_str,end_c,&start,&end)<0)
+    return -1;
+
   if (alg == DIGEST_SHA1) {
     if (crypto_digest(digest, start, end-start)) {
       log_warn(LD_BUG,"couldn't compute digest");
@@ -3480,6 +3538,24 @@ router_get_hash_impl(const char *s, char *digest,
   return 0;
 }
 
+/** As router_get_hash_impl, but compute all hashes. */
+static int
+router_get_hashes_impl(const char *s, digests_t *digests,
+                       const char *start_str,
+                       const char *end_str, char end_c)
+{
+  const char *start=NULL, *end=NULL;
+  if (router_get_hash_impl_helper(s,start_str,end_str,end_c,&start,&end)<0)
+    return -1;
+
+  if (crypto_digest_all(digests, start, end-start)) {
+    log_warn(LD_BUG,"couldn't compute digests");
+    return -1;
+  }
+
+  return 0;
+}
+
 /** DOCDOC Assuming that s starts with a microdesc, return the start of the
  * *NEXT* one. */
 static const char *
diff --git a/src/test/test_dir.c b/src/test/test_dir.c
index 8e566e2..b1fac68 100644
--- a/src/test/test_dir.c
+++ b/src/test/test_dir.c
@@ -571,6 +571,7 @@ test_dir_v3_networkstatus(void)
 
   time_t now = time(NULL);
   networkstatus_voter_info_t *voter;
+  document_signature_t *sig;
   networkstatus_t *vote=NULL, *v1=NULL, *v2=NULL, *v3=NULL, *con=NULL;
   vote_routerstatus_t *vrs;
   routerstatus_t *rs;
@@ -946,20 +947,25 @@ test_dir_v3_networkstatus(void)
   /* Check signatures.  the first voter is a pseudo-entry with a legacy key.
    * The second one hasn't signed.  The fourth one has signed: validate it. */
   voter = smartlist_get(con->voters, 1);
-  test_assert(!voter->signature);
-  test_assert(!voter->good_signature);
-  test_assert(!voter->bad_signature);
+  test_eq(smartlist_len(voter->sigs), 0);
+#if 0
+  sig = smartlist_get(voter->sigs, 1);
+  test_assert(!sig->signature);
+  test_assert(!sig->good_signature);
+  test_assert(!sig->bad_signature);
+#endif
 
   voter = smartlist_get(con->voters, 3);
-  test_assert(voter->signature);
-  test_assert(!voter->good_signature);
-  test_assert(!voter->bad_signature);
-  test_assert(!networkstatus_check_voter_signature(con,
-                                               smartlist_get(con->voters, 3),
-                                               cert3));
-  test_assert(voter->signature);
-  test_assert(voter->good_signature);
-  test_assert(!voter->bad_signature);
+  test_eq(smartlist_len(voter->sigs), 1);
+  sig = smartlist_get(voter->sigs, 0);
+  test_assert(sig->signature);
+  test_assert(!sig->good_signature);
+  test_assert(!sig->bad_signature);
+
+  test_assert(!networkstatus_check_document_signature(con, sig, cert3));
+  test_assert(sig->signature);
+  test_assert(sig->good_signature);
+  test_assert(!sig->bad_signature);
 
   {
     const char *msg=NULL;
@@ -984,10 +990,8 @@ test_dir_v3_networkstatus(void)
     test_assert(con3);
 
     /* All three should have the same digest. */
-    test_memeq(con->networkstatus_digest, con2->networkstatus_digest,
-               DIGEST_LEN);
-    test_memeq(con->networkstatus_digest, con3->networkstatus_digest,
-               DIGEST_LEN);
+    test_memeq(&con->digests, &con2->digests, sizeof(digests_t));
+    test_memeq(&con->digests, &con3->digests, sizeof(digests_t));
 
     /* Extract a detached signature from con3. */
     detached_text1 = networkstatus_get_detached_signatures(con3);
@@ -1000,12 +1004,20 @@ test_dir_v3_networkstatus(void)
     test_eq(dsig1->valid_after, con3->valid_after);
     test_eq(dsig1->fresh_until, con3->fresh_until);
     test_eq(dsig1->valid_until, con3->valid_until);
-    test_memeq(dsig1->networkstatus_digest, con3->networkstatus_digest,
-               DIGEST_LEN);
-    test_eq(1, smartlist_len(dsig1->signatures));
-    voter = smartlist_get(dsig1->signatures, 0);
-    test_memeq(voter->identity_digest, cert1->cache_info.identity_digest,
-               DIGEST_LEN);
+    {
+      digests_t *dsig_digests = strmap_get(dsig1->digests, "ns");
+      test_assert(dsig_digests);
+      test_memeq(dsig_digests->d[DIGEST_SHA1], con3->digests.d[DIGEST_SHA1],
+                 DIGEST_LEN);
+    }
+    {
+      smartlist_t *dsig_signatures = strmap_get(dsig1->signatures, "ns");
+      test_assert(dsig_signatures);
+      test_eq(1, smartlist_len(dsig_signatures));
+      sig = smartlist_get(dsig_signatures, 0);
+      test_memeq(sig->identity_digest, cert1->cache_info.identity_digest,
+                 DIGEST_LEN);
+    }
 
     /* Try adding it to con2. */
     detached_text2 = networkstatus_get_detached_signatures(con2);
@@ -1023,7 +1035,8 @@ test_dir_v3_networkstatus(void)
         printf("%s\n", hd);
       });
     */
-    test_eq(2, smartlist_len(dsig2->signatures));
+    test_eq(2,
+            smartlist_len((smartlist_t*)strmap_get(dsig2->signatures, "ns")));
 
     /* Try adding to con2 twice; verify that nothing changes. */
     test_eq(0, networkstatus_add_detached_signatures(con2, dsig1, &msg));
@@ -1031,13 +1044,14 @@ test_dir_v3_networkstatus(void)
     /* Add to con. */
     test_eq(2, networkstatus_add_detached_signatures(con, dsig2, &msg));
     /* Check signatures */
-    test_assert(!networkstatus_check_voter_signature(con,
-                                               smartlist_get(con->voters, 1),
-                                               cert2));
-    test_assert(!networkstatus_check_voter_signature(con,
-                                               smartlist_get(con->voters, 2),
-                                               cert1));
-
+    voter = smartlist_get(con->voters, 1);
+    sig = smartlist_get(voter->sigs, 0);
+    test_assert(sig);
+    test_assert(!networkstatus_check_document_signature(con, sig, cert2));
+    voter = smartlist_get(con->voters, 2);
+    sig = smartlist_get(voter->sigs, 0);
+    test_assert(sig);
+    test_assert(!networkstatus_check_document_signature(con, sig, cert1));
   }
 
  done:
-- 
1.5.6.5




More information about the tor-commits mailing list