diff --git a/doc/TODO b/doc/TODO
index aff8547886..db08e01c2d 100644
--- a/doc/TODO
+++ b/doc/TODO
@@ -56,7 +56,7 @@ N . Document transport and natdport
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 @@ Things we'd like to do in 0.2.0.x:
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 @@ Things we'd like to do in 0.2.0.x:
- 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
diff --git a/src/common/util.c b/src/common/util.c
index 18d9fcba2c..d4a30a1459 100644
--- a/src/common/util.c
+++ b/src/common/util.c
@@ -1381,8 +1381,8 @@ append_bytes_to_file(const char *fname, const char *str, size_t len,
/** Read the contents of filename into a newly allocated
* string; return the string on success or NULL on failure.
*
- * If size_out is provided, store the length of the result in
- * size_out.
+ * If stat_out is provided, store the result of stat()ing the
+ * file into stat_out.
*
* If flags & RFTS_BIN, open the file in binary mode.
* If flags & RFTS_IGNORE_MISSING, don't warn if the file
diff --git a/src/or/config.c b/src/or/config.c
index 69de3a51bd..d789ea15e5 100644
--- a/src/or/config.c
+++ b/src/or/config.c
@@ -261,6 +261,7 @@ static config_var_t _option_vars[] = {
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 @@ options_act(or_options_t *old_options)
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 @@ parse_authority_type_from_list(smartlist_t *list, authority_type_t *auth,
*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 @@ options_validate(or_options_t *old_options, or_options_t *options,
"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 @@ parse_dir_server_line(const char *line, int validate_only)
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 @@ parse_dir_server_line(const char *line, int validate_only)
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);
diff --git a/src/or/or.h b/src/or/or.h
index 59dd228d05..196705a1cb 100644
--- a/src/or/or.h
+++ b/src/or/or.h
@@ -1334,9 +1334,10 @@ typedef enum {
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 @@ typedef struct {
* 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 @@ typedef struct trusted_dir_server_t {
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 @@ typedef struct trusted_dir_server_t {
/** 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 @@ routerstatus_t *router_pick_trusteddirserver(authority_type_t type,
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 @@ int getinfo_helper_networkstatus(control_connection_t *conn,
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 @@ networkstatus_t *networkstatus_parse_from_string(const char *s);
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
diff --git a/src/or/router.c b/src/or/router.c
index 826ccc0eb6..c8efd83416 100644
--- a/src/or/router.c
+++ b/src/or/router.c
@@ -38,6 +38,11 @@ static crypto_pk_env_t *lastonionkey=NULL;
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 k. Does not affect lastonionkey;
* to update onionkey correctly, call rotate_onion_key().
@@ -170,46 +175,48 @@ rotate_onion_key(void)
log_warn(LD_GENERAL, "Couldn't rotate onion key.");
}
-/** Try to read an RSA key from fname. If fname doesn't exist,
- * create a new RSA key and save it in fname. 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 (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;
+ 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);
}
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 @@ init_key_from_file(const char *fname)
return NULL;
}
+/** Try to read an RSA key from fname. If fname doesn't exist,
+ * create a new RSA key and save it in fname. 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 @@ init_keys(void)
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 @@ router_free_all(void)
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));
diff --git a/src/or/routerlist.c b/src/or/routerlist.c
index d09bd7f234..d27f69b497 100644
--- a/src/or/routerlist.c
+++ b/src/or/routerlist.c
@@ -49,6 +49,8 @@ static void router_dir_info_changed(void);
/** 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 @@ router_reload_networkstatus(void)
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 @@ router_get_trusteddirserver_by_digest(const char *digest)
return NULL;
}
+/** Return the trusted_dir_server_t for the directory authority whose identity
+ * key hashes to digest, 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 retry_if_no_servers is non-zero,
* set them all as running again, and try again.
@@ -3477,6 +3580,8 @@ add_trusted_dir_server(const char *nickname, const char *address,
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);
diff --git a/src/or/routerparse.c b/src/or/routerparse.c
index f590eec1b1..b14e868071 100644
--- a/src/or/routerparse.c
+++ b/src/or/routerparse.c
@@ -1289,16 +1289,19 @@ authority_cert_free(authority_cert_t *cert)
/** Parse a key certificate from s; point end-of-string 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 = strstr(s, "\n-----END SIGNATURE-----\n");
+ char *eos;
size_t len;
+ trusted_dir_server_t *ds;
+
+ 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 @@ authority_cert_parse_from_string(const char *s, char **end_of_string)
}
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 @@ authority_cert_parse_from_string(const char *s, char **end_of_string)
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 @@ authority_cert_parse_from_string(const char *s, char **end_of_string)
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);