[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[or-cvs] r10293: More v3 directory code: have authorities load certificates; (in tor/trunk: . doc src/common src/or)



Author: nickm
Date: 2007-05-22 13:58:25 -0400 (Tue, 22 May 2007)
New Revision: 10293

Modified:
   tor/trunk/
   tor/trunk/doc/TODO
   tor/trunk/src/common/util.c
   tor/trunk/src/or/config.c
   tor/trunk/src/or/or.h
   tor/trunk/src/or/router.c
   tor/trunk/src/or/routerlist.c
   tor/trunk/src/or/routerparse.c
Log:
 r12898@catbus:  nickm | 2007-05-22 13:11:04 -0400
 More v3 directory code: have authorities load certificates; have everybody store certificates to disk and load them; provide a way to configure v3 authorities.



Property changes on: tor/trunk
___________________________________________________________________
 svk:merge ticket from /tor/trunk [r12898] on 8246c3cf-6607-4228-993b-4d95d33730f1

Modified: tor/trunk/doc/TODO
===================================================================
--- tor/trunk/doc/TODO	2007-05-22 15:49:14 UTC (rev 10292)
+++ tor/trunk/doc/TODO	2007-05-22 17:58:25 UTC (rev 10293)
@@ -56,7 +56,7 @@
 
 Things we'd like to do in 0.2.0.x:
   - Proposals:
-    . 101: Voting on the Tor Directory System
+    . 101: Voting on the Tor Directory System (plus 103)
       o Prepare ASAP for new voting formats
         o Don't flip out with warnings when voting-related URLs are
           uploaded/downloaded.
@@ -68,16 +68,21 @@
           o Parse key certificates
           - Parse votes and consensuses
           - Unit tests for above
-        - Code to manage key certificates
-          - Cache on disk
-          - Download as needed
+        . Code to manage key certificates
+          o Generate certificates
+          o Authorities load certificates
+          o Clients cache certificates on disk
+          - Download as needed.
           - Serve list as needed.
-          - Avoid double-checking signatures every time we get a vote.
+          o Avoid double-checking signatures every time we get a vote.
+          - Warn about expired stuff.
         - Code to generate votes
         - Code to generate consensus from a list of votes
         - Add a signature to a consensus.
         - Code to check signatures on a consensus
         - Push/pull documents as appropriate.
+        o Have clients know which authorities are v3 authorities, and what
+          their keys are.
       - Start caching consensus documents once authorities make them
       - Start downloading and using consensus documents once caches serve them
     . 104: Long and Short Router Descriptors (by Jun 1)
@@ -98,8 +103,8 @@
     - 105: Version negotiation for the Tor protocol (finalize by Jun 1)
     - 108: Base "Stable" Flag on Mean Time Between Failures
     - 109: No more than one server per IP address
-    - 103: Splitting identity key from regularly used signing key
-      - Merge with 101 into a new dir-spec.txt
+    o 103: Splitting identity key from regularly used signing key
+      o Merge with 101 into a new dir-spec.txt
     - 113: Simplifying directory authority administration
     - 110: prevent infinite-length circuits (phase one)
       - servers should recognize relay_extend cells and pass them

Modified: tor/trunk/src/common/util.c
===================================================================
--- tor/trunk/src/common/util.c	2007-05-22 15:49:14 UTC (rev 10292)
+++ tor/trunk/src/common/util.c	2007-05-22 17:58:25 UTC (rev 10293)
@@ -1381,8 +1381,8 @@
 /** Read the contents of <b>filename</b> into a newly allocated
  * string; return the string on success or NULL on failure.
  *
- * If <b>size_out</b> is provided, store the length of the result in
- * <b>size_out</b>.
+ * If <b>stat_out</b> is provided, store the result of stat()ing the
+ * file into <b>stat_out</b>.
  *
  * If <b>flags</b> &amp; RFTS_BIN, open the file in binary mode.
  * If <b>flags</b> &amp; RFTS_IGNORE_MISSING, don't warn if the file

Modified: tor/trunk/src/or/config.c
===================================================================
--- tor/trunk/src/or/config.c	2007-05-22 15:49:14 UTC (rev 10292)
+++ tor/trunk/src/or/config.c	2007-05-22 17:58:25 UTC (rev 10293)
@@ -261,6 +261,7 @@
   VAR("User",                STRING,   User,                 NULL),
   VAR("V1AuthoritativeDirectory",BOOL, V1AuthoritativeDir,   "0"),
   VAR("V2AuthoritativeDirectory",BOOL, V2AuthoritativeDir,   "0"),
+  VAR("V3AuthoritativeDirectory",BOOL, V3AuthoritativeDir,   "0"),
   VAR("VersioningAuthoritativeDirectory",BOOL,VersioningAuthoritativeDir, "0"),
   VAR("VirtualAddrNetwork",  STRING,   VirtualAddrNetwork,   "127.192.0.0/10"),
   VAR("__AllDirActionsPrivate",BOOL,   AllDirActionsPrivate, "0"),
@@ -1047,6 +1048,7 @@
       if (dns_reset())
         return -1;
     }
+    /* XXXX020 init_keys() again if v3authoritativedir is newly set. */
   }
 
   /* Check if we need to parse and add the EntryNodes config option. */
@@ -2346,6 +2348,8 @@
       *auth |= V1_AUTHORITY | V2_AUTHORITY;
     else if (!strcasecmp(string, "v2"))
       *auth |= V2_AUTHORITY;
+    else if (!strcasecmp(string, "v3"))
+      *auth |= V3_AUTHORITY;
     else if (!strcasecmp(string, "bridge"))
       *auth |= BRIDGE_AUTHORITY;
     else if (!strcasecmp(string, "hidserv"))
@@ -2580,6 +2584,8 @@
                "extra-info documents. Setting DownloadExtraInfo.");
       options->DownloadExtraInfo = 1;
     }
+    /* XXXX020 Check that at least one of Bridge/HS/V1/V2/V2{AoritativeDir}
+     * is set. */
   }
 
   if (options->AuthoritativeDir && !options->DirPort)
@@ -3588,6 +3594,7 @@
   char *addrport=NULL, *address=NULL, *nickname=NULL, *fingerprint=NULL;
   uint16_t dir_port = 0, or_port = 0;
   char digest[DIGEST_LEN];
+  char v3_digest[DIGEST_LEN];
   authority_type_t type = V2_AUTHORITY;
   int is_not_hidserv_authority = 0, is_not_v2_authority = 0;
 
@@ -3625,6 +3632,15 @@
       if (!ok)
         log_warn(LD_CONFIG, "Invalid orport '%s' on DirServer line.",
                  portstring);
+    } else if (!strcasecmpstart(flag, "v3ident=")) {
+      char *idstr = flag + strlen("v3ident=");
+      if (strlen(idstr) != HEX_DIGEST_LEN ||
+          base16_decode(v3_digest, DIGEST_LEN, idstr, HEX_DIGEST_LEN)<0) {
+        log_warn(LD_CONFIG, "Bad v3 identity digest '%s' on DirServer line",
+                 flag);
+      } else {
+        type |= V3_AUTHORITY;
+      }
     } else {
       log_warn(LD_CONFIG, "Unrecognized flag '%s' on DirServer line",
                flag);

Modified: tor/trunk/src/or/or.h
===================================================================
--- tor/trunk/src/or/or.h	2007-05-22 15:49:14 UTC (rev 10292)
+++ tor/trunk/src/or/or.h	2007-05-22 17:58:25 UTC (rev 10293)
@@ -1334,9 +1334,10 @@
   NO_AUTHORITY      = 0,
   V1_AUTHORITY      = 1 << 0,
   V2_AUTHORITY      = 1 << 1,
-  HIDSERV_AUTHORITY = 1 << 2,
-  BRIDGE_AUTHORITY  = 1 << 3,
-  EXTRAINFO_CACHE   = 1 << 4,  /* not precisely an authority type. */
+  V3_AUTHORITY      = 1 << 2,
+  HIDSERV_AUTHORITY = 1 << 3,
+  BRIDGE_AUTHORITY  = 1 << 4,
+  EXTRAINFO_CACHE   = 1 << 5,  /* not precisely an authority type. */
 } authority_type_t;
 
 #define CRYPT_PATH_MAGIC 0x70127012u
@@ -1758,6 +1759,8 @@
                            * for version 1 directories? */
   int V2AuthoritativeDir; /**< Boolean: is this an authoritative directory
                            * for version 2 directories? */
+  int V3AuthoritativeDir; /**< Boolean: is this an authoritative directory
+                           * for version 3 directories? */
   int HSAuthoritativeDir; /**< Boolean: does this an authoritative directory
                            * handle hidden service requests? */
   int HSAuthorityRecordStats; /**< Boolean: does this HS authoritative
@@ -3057,6 +3060,9 @@
   uint16_t dir_port; /**< Directory port. */
   uint16_t or_port; /**< OR port: Used for tunneling connections. */
   char digest[DIGEST_LEN]; /**< Digest of identity key. */
+  char v3_identity_digest[DIGEST_LEN]; /**< Digest of v3 (authority only,
+                                        * high-security) identity key. */
+
   unsigned int is_running:1; /**< True iff we think this server is running. */
 
   /** True iff this server has accepted the most recent server descriptor
@@ -3066,6 +3072,8 @@
   /** DOCDOC */
   authority_type_t type;
 
+  authority_cert_t *v3_cert; /**< V3 key certificate for this authority */
+
   int n_networkstatus_failures; /**< How many times have we asked for this
                                  * server's network-status unsuccessfully? */
   local_routerstatus_t fake_status; /**< Used when we need to pass this trusted
@@ -3088,6 +3096,8 @@
                                              int retry_if_no_servers);
 trusted_dir_server_t *router_get_trusteddirserver_by_digest(
      const char *digest);
+trusted_dir_server_t *trusteddirserver_get_by_v3_auth_digest(
+     const char *digest);
 void routerlist_add_family(smartlist_t *sl, routerinfo_t *router);
 void add_nickname_list_to_smartlist(smartlist_t *sl, const char *list,
                                     int must_be_running);
@@ -3197,6 +3207,10 @@
 void routerlist_assert_ok(routerlist_t *rl);
 void routerlist_check_bug_417(void);
 
+int trusted_dirs_reload_certs(void);
+int trusted_dirs_load_certs_from_string(const char *contents, int from_store);
+void trusted_dirs_flush_certs_to_disk(void);
+
 /********************************* routerparse.c ************************/
 
 #define MAX_STATUS_TAG_LEN 32
@@ -3278,7 +3292,7 @@
 
 void authority_cert_free(authority_cert_t *cert);
 authority_cert_t *authority_cert_parse_from_string(const char *s,
-                                                   char **end_of_string);
+                                                   const char **end_of_string);
 
 #endif
 

Modified: tor/trunk/src/or/router.c
===================================================================
--- tor/trunk/src/or/router.c	2007-05-22 15:49:14 UTC (rev 10292)
+++ tor/trunk/src/or/router.c	2007-05-22 17:58:25 UTC (rev 10293)
@@ -38,6 +38,11 @@
 static crypto_pk_env_t *identitykey=NULL;
 /** Digest of identitykey. */
 static char identitykey_digest[DIGEST_LEN];
+/** Signing key used for v3 directory material; only set for authorities. */
+static crypto_pk_env_t *authority_signing_key = NULL;
+/** Key certificate to authenticate v3 directory material; only set for
+ * authorities. */
+static authority_cert_t *authority_key_certificate = NULL;
 
 /** Replace the current onion key with <b>k</b>.  Does not affect lastonionkey;
  * to update onionkey correctly, call rotate_onion_key().
@@ -170,46 +175,48 @@
   log_warn(LD_GENERAL, "Couldn't rotate onion key.");
 }
 
-/** Try to read an RSA key from <b>fname</b>.  If <b>fname</b> doesn't exist,
- * create a new RSA key and save it in <b>fname</b>.  Return the read/created
- * key, or NULL on error.
- */
-crypto_pk_env_t *
-init_key_from_file(const char *fname)
+/** DOCDOC */
+static crypto_pk_env_t *
+init_key_from_file_impl(const char *fname, int generate, int severity)
 {
   crypto_pk_env_t *prkey = NULL;
   FILE *file = NULL;
 
   if (!(prkey = crypto_new_pk_env())) {
-    log_err(LD_GENERAL,"Error constructing key");
+    log(severity, LD_GENERAL,"Error constructing key");
     goto error;
   }
 
   switch (file_status(fname)) {
     case FN_DIR:
     case FN_ERROR:
-      log_err(LD_FS,"Can't read key from \"%s\"", fname);
+      log(severity, LD_FS,"Can't read key from \"%s\"", fname);
       goto error;
     case FN_NOENT:
-      log_info(LD_GENERAL, "No key found in \"%s\"; generating fresh key.",
-               fname);
-      if (crypto_pk_generate_key(prkey)) {
-        log_err(LD_GENERAL,"Error generating onion key");
-        goto error;
+      if (generate) {
+        log_info(LD_GENERAL, "No key found in \"%s\"; generating fresh key.",
+                 fname);
+        if (crypto_pk_generate_key(prkey)) {
+          log(severity, LD_GENERAL,"Error generating onion key");
+          goto error;
+        }
+        if (crypto_pk_check_key(prkey) <= 0) {
+          log(severity, LD_GENERAL,"Generated key seems invalid");
+          goto error;
+        }
+        log_info(LD_GENERAL, "Generated key seems valid");
+        if (crypto_pk_write_private_key_to_filename(prkey, fname)) {
+          log(severity, LD_FS,
+              "Couldn't write generated key to \"%s\".", fname);
+          goto error;
+        }
+      } else {
+        log_info(LD_GENERAL, "No key found in \"%s\"", fname);
       }
-      if (crypto_pk_check_key(prkey) <= 0) {
-        log_err(LD_GENERAL,"Generated key seems invalid");
-        goto error;
-      }
-      log_info(LD_GENERAL, "Generated key seems valid");
-      if (crypto_pk_write_private_key_to_filename(prkey, fname)) {
-        log_err(LD_FS,"Couldn't write generated key to \"%s\".", fname);
-        goto error;
-      }
       return prkey;
     case FN_FILE:
       if (crypto_pk_read_private_key_from_filename(prkey, fname)) {
-        log_err(LD_GENERAL,"Error loading private key.");
+        log(severity, LD_GENERAL,"Error loading private key.");
         goto error;
       }
       return prkey;
@@ -225,6 +232,71 @@
   return NULL;
 }
 
+/** Try to read an RSA key from <b>fname</b>.  If <b>fname</b> doesn't exist,
+ * create a new RSA key and save it in <b>fname</b>.  Return the read/created
+ * key, or NULL on error.
+ */
+crypto_pk_env_t *
+init_key_from_file(const char *fname)
+{
+  return init_key_from_file_impl(fname, 1, LOG_ERR);
+}
+
+/** DOCDOC; XXXX020 maybe move to dirserv.c */
+static void
+init_v3_authority_keys(const char *keydir)
+{
+  char *fname = NULL, *cert = NULL;
+  const char *eos = NULL;
+  size_t fname_len = strlen(keydir) + 64;
+  crypto_pk_env_t *signing_key = NULL;
+  authority_cert_t *parsed = NULL;
+
+  fname = tor_malloc(fname_len);
+  tor_snprintf(fname, fname_len, "%s"PATH_SEPARATOR"authority_signing_key",
+               keydir);
+  signing_key = init_key_from_file_impl(fname, 0, LOG_INFO);
+  if (!signing_key) {
+    log_warn(LD_DIR, "No version 3 directory key found in %s", fname);
+    goto done;
+  }
+  tor_snprintf(fname, fname_len, "%s"PATH_SEPARATOR"authority_certificate",
+               keydir);
+  cert = read_file_to_str(fname, 0, NULL);
+  if (!cert) {
+    log_warn(LD_DIR, "Signing key found, but no certificate found in %s",
+               fname);
+    goto done;
+  }
+  parsed = authority_cert_parse_from_string(cert, &eos);
+  if (!parsed) {
+    log_warn(LD_DIR, "Unable to parse certificate in %s", fname);
+    goto done;
+  }
+  if (crypto_pk_cmp_keys(signing_key, parsed->signing_key) != 0) {
+    log_warn(LD_DIR, "Stored signing key does not match signing key in "
+             "certificate");
+    goto done;
+  }
+  parsed->cache_info.signed_descriptor_body = cert;
+  parsed->cache_info.signed_descriptor_len = eos-cert;
+  cert = NULL;
+
+  authority_key_certificate = parsed;
+  authority_signing_key = signing_key;
+  parsed = NULL;
+  signing_key = NULL;
+
+ done:
+  tor_free(fname);
+  tor_free(cert);
+  if (signing_key)
+    crypto_free_pk_env(signing_key);
+  if (parsed)
+    authority_cert_free(parsed);
+}
+
+
 /** Initialize all OR private keys, and the TLS context, as necessary.
  * On OPs, this only initializes the tls context. Return 0 on success,
  * or -1 if Tor should die.
@@ -282,6 +354,11 @@
   prkey = init_key_from_file(keydir);
   if (!prkey) return -1;
   set_identity_key(prkey);
+
+  /* 1b. Read v3 directory authority key/cert information. */
+  if (authdir_mode(options) && options->V3AuthoritativeDir)
+    init_v3_authority_keys(keydir);
+
   /* 2. Read onion key.  Make it if none is found. */
   tor_snprintf(keydir,sizeof(keydir),
              "%s"PATH_SEPARATOR"keys"PATH_SEPARATOR"secret_onion_key",datadir);
@@ -1592,6 +1669,10 @@
     routerinfo_free(desc_routerinfo);
   if (desc_extrainfo)
     extrainfo_free(desc_extrainfo);
+  if (authority_signing_key)
+    crypto_free_pk_env(authority_signing_key);
+  if (authority_key_certificate)
+    authority_cert_free(authority_key_certificate);
 
   if (warned_nonexistent_family) {
     SMARTLIST_FOREACH(warned_nonexistent_family, char *, cp, tor_free(cp));

Modified: tor/trunk/src/or/routerlist.c
===================================================================
--- tor/trunk/src/or/routerlist.c	2007-05-22 15:49:14 UTC (rev 10292)
+++ tor/trunk/src/or/routerlist.c	2007-05-22 17:58:25 UTC (rev 10293)
@@ -49,6 +49,8 @@
 /** Global list of a trusted_dir_server_t object for each trusted directory
  * server. */
 static smartlist_t *trusted_dir_servers = NULL;
+/** DOCDOC */
+static int trusted_dir_servers_certs_changed = 0;
 
 /** Global list of all of the routers that we know about. */
 static routerlist_t *routerlist = NULL;
@@ -163,6 +165,89 @@
   return 0;
 }
 
+/** DOCDOC */
+int
+trusted_dirs_reload_certs(void)
+{
+  char filename[512];
+  char *contents;
+  int r;
+
+  tor_snprintf(filename,sizeof(filename),"%s"PATH_SEPARATOR"cached-certs",
+               get_options()->DataDirectory);
+  contents = read_file_to_str(filename, 0, NULL);
+  if (!contents)
+    return -1;
+  r = trusted_dirs_load_certs_from_string(contents, 1);
+  tor_free(contents);
+  return r;
+}
+
+/** DOCDOC */
+int
+trusted_dirs_load_certs_from_string(const char *contents, int from_store)
+{
+  trusted_dir_server_t *ds;
+  const char *s, *eos;
+
+  for (s = contents; *s; s = eos) {
+    authority_cert_t *cert = authority_cert_parse_from_string(s, &eos);
+    if (!cert)
+      break;
+    ds = trusteddirserver_get_by_v3_auth_digest(
+                                       cert->cache_info.identity_digest);
+    if (!ds) {
+      log_info(LD_DIR, "Found cached certificate whose key didn't match "
+               "any v3 authority we recognized; skipping.");
+      authority_cert_free(cert);
+      continue;
+    }
+
+    if (ds->v3_cert) {
+      if (ds->v3_cert->expires < cert->expires) {
+        authority_cert_free(ds->v3_cert);
+      } else {
+        authority_cert_free(cert);
+        continue;
+      }
+    }
+
+    cert->cache_info.signed_descriptor_body = tor_strndup(s, eos-s);
+    cert->cache_info.signed_descriptor_len = eos-s;
+    ds->v3_cert = cert;
+    if (!from_store)
+      trusted_dir_servers_certs_changed = 1;
+  }
+  return 0;
+}
+
+/** DOCDOC */
+void
+trusted_dirs_flush_certs_to_disk(void)
+{
+  char filename[512];
+  smartlist_t *chunks = smartlist_create();
+
+  tor_snprintf(filename,sizeof(filename),"%s"PATH_SEPARATOR"cached-certs",
+               get_options()->DataDirectory);
+  SMARTLIST_FOREACH(trusted_dir_servers, trusted_dir_server_t *, ds,
+  {
+      if (ds->v3_cert) {
+        sized_chunk_t *c = tor_malloc(sizeof(sized_chunk_t));
+        c->bytes = ds->v3_cert->cache_info.signed_descriptor_body;
+        c->len = ds->v3_cert->cache_info.signed_descriptor_len;
+        smartlist_add(chunks, c);
+      }
+  });
+  if (write_chunks_to_file(filename, chunks, 0)) {
+    log_warn(LD_FS, "Error writing certificates to disk.");
+  }
+  SMARTLIST_FOREACH(chunks, sized_chunk_t *, c, tor_free(c));
+  smartlist_free(chunks);
+
+  trusted_dir_servers_certs_changed = 0;
+}
+
 /* Router descriptor storage.
  *
  * Routerdescs are stored in a big file, named "cached-routers".  As new
@@ -573,6 +658,24 @@
   return NULL;
 }
 
+/** Return the trusted_dir_server_t for the directory authority whose identity
+ * key hashes to <b>digest</b>, or NULL if no such authority is known.
+ */
+trusted_dir_server_t *
+trusteddirserver_get_by_v3_auth_digest(const char *digest)
+{
+  if (!trusted_dir_servers)
+    return NULL;
+
+  SMARTLIST_FOREACH(trusted_dir_servers, trusted_dir_server_t *, ds,
+     {
+       if (!memcmp(ds->v3_identity_digest, digest, DIGEST_LEN))
+         return ds;
+     });
+
+  return NULL;
+}
+
 /** Try to find a running trusted dirserver. If there are no running
  * trusted dirservers and <b>retry_if_no_servers</b> is non-zero,
  * set them all as running again, and try again.
@@ -3477,6 +3580,8 @@
 static void
 trusted_dir_server_free(trusted_dir_server_t *ds)
 {
+  if (ds->v3_cert)
+    authority_cert_free(ds->v3_cert);
   tor_free(ds->nickname);
   tor_free(ds->description);
   tor_free(ds->address);

Modified: tor/trunk/src/or/routerparse.c
===================================================================
--- tor/trunk/src/or/routerparse.c	2007-05-22 15:49:14 UTC (rev 10292)
+++ tor/trunk/src/or/routerparse.c	2007-05-22 17:58:25 UTC (rev 10293)
@@ -1289,16 +1289,19 @@
 /** Parse a key certificate from <b>s</b>; point <b>end-of-string</b> to
  * the first character after the certificate. */
 authority_cert_t *
-authority_cert_parse_from_string(const char *s, char **end_of_string)
+authority_cert_parse_from_string(const char *s, const char **end_of_string)
 {
   authority_cert_t *cert = NULL;
   smartlist_t *tokens = NULL;
   char digest[DIGEST_LEN];
   directory_token_t *tok;
   char fp_declared[DIGEST_LEN];
+  char *eos;
+  size_t len;
+  trusted_dir_server_t *ds;
 
-  char *eos = strstr(s, "\n-----END SIGNATURE-----\n");
-  size_t len;
+  s = eat_whitespace(s);
+  eos = strstr(s, "\n-----END SIGNATURE-----\n");
   if (! eos) {
     log_warn(LD_DIR, "No end-of-signature found on key certificate");
     return NULL;
@@ -1324,6 +1327,7 @@
   }
 
   cert = tor_malloc_zero(sizeof(authority_cert_t));
+  memcpy(cert->cache_info.signed_descriptor_digest, digest, DIGEST_LEN);
 
   tok = find_first_by_keyword(tokens, K_DIR_SIGNING_KEY);
   tor_assert(tok && tok->key);
@@ -1371,11 +1375,20 @@
     goto err;
   }
 
-  /* XXXXX This doesn't check whether the key is an authority. IS that what we
-   * want? */
-  if (check_signature_token(digest, tok, cert->identity_key, 0,
-                            "key certificate")) {
-    goto err;
+  /* If we already have this cert, don't bother checking the signature. */
+  ds = trusteddirserver_get_by_v3_auth_digest(
+                                     cert->cache_info.identity_digest);
+  if (ds && ds->v3_cert &&
+      ds->v3_cert->cache_info.signed_descriptor_len == len &&
+      ds->v3_cert->cache_info.signed_descriptor_body &&
+      ! memcmp(s, ds->v3_cert->cache_info.signed_descriptor_body, len)) {
+    log_debug(LD_DIR, "We already checked the signature on this certificate;"
+              " no need to do so again.");
+  } else {
+    if (check_signature_token(digest, tok, cert->identity_key, 0,
+                              "key certificate")) {
+      goto err;
+    }
   }
 
   cert->cache_info.signed_descriptor_len = len;
@@ -1383,7 +1396,10 @@
   memcpy(cert->cache_info.signed_descriptor_body, s, len);
   cert->cache_info.signed_descriptor_body[len] = 0;
   cert->cache_info.saved_location = SAVED_NOWHERE;
-  *end_of_string = eos;
+
+  if (end_of_string) {
+    *end_of_string = eat_whitespace(eos);
+  }
   return cert;
  err:
   authority_cert_free(cert);