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);