Implement proposal 228: cross-certification with onion keys

Routers now use TAP and ntor onion keys to sign their identity keys,
and put these signatures in their descriptors.  That allows other
parties to be confident that the onion keys are indeed controlled by
the router that generated the descriptor.
This commit is contained in:
Nick Mathewson 2014-10-01 11:54:07 -04:00
parent fe5d2477aa
commit efa21bb941
14 changed files with 417 additions and 54 deletions

View File

@ -830,7 +830,7 @@ crypto_pk_public_exponent_ok(crypto_pk_t *env)
* Note that this may leak information about the keys through timing. * Note that this may leak information about the keys through timing.
*/ */
int int
crypto_pk_cmp_keys(crypto_pk_t *a, crypto_pk_t *b) crypto_pk_cmp_keys(const crypto_pk_t *a, const crypto_pk_t *b)
{ {
int result; int result;
char a_is_non_null = (a != NULL) && (a->key != NULL); char a_is_non_null = (a != NULL) && (a->key != NULL);
@ -856,19 +856,19 @@ crypto_pk_cmp_keys(crypto_pk_t *a, crypto_pk_t *b)
* Note that this may leak information about the keys through timing. * Note that this may leak information about the keys through timing.
*/ */
int int
crypto_pk_eq_keys(crypto_pk_t *a, crypto_pk_t *b) crypto_pk_eq_keys(const crypto_pk_t *a, const crypto_pk_t *b)
{ {
return (crypto_pk_cmp_keys(a, b) == 0); return (crypto_pk_cmp_keys(a, b) == 0);
} }
/** Return the size of the public key modulus in <b>env</b>, in bytes. */ /** Return the size of the public key modulus in <b>env</b>, in bytes. */
size_t size_t
crypto_pk_keysize(crypto_pk_t *env) crypto_pk_keysize(const crypto_pk_t *env)
{ {
tor_assert(env); tor_assert(env);
tor_assert(env->key); tor_assert(env->key);
return (size_t) RSA_size(env->key); return (size_t) RSA_size((RSA*)env->key);
} }
/** Return the size of the public key modulus of <b>env</b>, in bits. */ /** Return the size of the public key modulus of <b>env</b>, in bits. */
@ -997,7 +997,7 @@ crypto_pk_private_decrypt(crypto_pk_t *env, char *to,
* at least the length of the modulus of <b>env</b>. * at least the length of the modulus of <b>env</b>.
*/ */
int int
crypto_pk_public_checksig(crypto_pk_t *env, char *to, crypto_pk_public_checksig(const crypto_pk_t *env, char *to,
size_t tolen, size_t tolen,
const char *from, size_t fromlen) const char *from, size_t fromlen)
{ {
@ -1069,7 +1069,7 @@ crypto_pk_public_checksig_digest(crypto_pk_t *env, const char *data,
* at least the length of the modulus of <b>env</b>. * at least the length of the modulus of <b>env</b>.
*/ */
int int
crypto_pk_private_sign(crypto_pk_t *env, char *to, size_t tolen, crypto_pk_private_sign(const crypto_pk_t *env, char *to, size_t tolen,
const char *from, size_t fromlen) const char *from, size_t fromlen)
{ {
int r; int r;
@ -1084,7 +1084,7 @@ crypto_pk_private_sign(crypto_pk_t *env, char *to, size_t tolen,
r = RSA_private_encrypt((int)fromlen, r = RSA_private_encrypt((int)fromlen,
(unsigned char*)from, (unsigned char*)to, (unsigned char*)from, (unsigned char*)to,
env->key, RSA_PKCS1_PADDING); (RSA*)env->key, RSA_PKCS1_PADDING);
if (r<0) { if (r<0) {
crypto_log_errors(LOG_WARN, "generating RSA signature"); crypto_log_errors(LOG_WARN, "generating RSA signature");
return -1; return -1;
@ -1298,7 +1298,7 @@ crypto_pk_get_digest(const crypto_pk_t *pk, char *digest_out)
unsigned char *buf = NULL; unsigned char *buf = NULL;
int len; int len;
len = i2d_RSAPublicKey(pk->key, &buf); len = i2d_RSAPublicKey((RSA*)pk->key, &buf);
if (len < 0 || buf == NULL) if (len < 0 || buf == NULL)
return -1; return -1;
if (crypto_digest(digest_out, (char*)buf, len) < 0) { if (crypto_digest(digest_out, (char*)buf, len) < 0) {

View File

@ -147,9 +147,9 @@ int crypto_pk_write_private_key_to_filename(crypto_pk_t *env,
const char *fname); const char *fname);
int crypto_pk_check_key(crypto_pk_t *env); int crypto_pk_check_key(crypto_pk_t *env);
int crypto_pk_cmp_keys(crypto_pk_t *a, crypto_pk_t *b); int crypto_pk_cmp_keys(const crypto_pk_t *a, const crypto_pk_t *b);
int crypto_pk_eq_keys(crypto_pk_t *a, crypto_pk_t *b); int crypto_pk_eq_keys(const crypto_pk_t *a, const crypto_pk_t *b);
size_t crypto_pk_keysize(crypto_pk_t *env); size_t crypto_pk_keysize(const crypto_pk_t *env);
int crypto_pk_num_bits(crypto_pk_t *env); int crypto_pk_num_bits(crypto_pk_t *env);
crypto_pk_t *crypto_pk_dup_key(crypto_pk_t *orig); crypto_pk_t *crypto_pk_dup_key(crypto_pk_t *orig);
crypto_pk_t *crypto_pk_copy_full(crypto_pk_t *orig); crypto_pk_t *crypto_pk_copy_full(crypto_pk_t *orig);
@ -161,11 +161,11 @@ int crypto_pk_public_encrypt(crypto_pk_t *env, char *to, size_t tolen,
int crypto_pk_private_decrypt(crypto_pk_t *env, char *to, size_t tolen, int crypto_pk_private_decrypt(crypto_pk_t *env, char *to, size_t tolen,
const char *from, size_t fromlen, const char *from, size_t fromlen,
int padding, int warnOnFailure); int padding, int warnOnFailure);
int crypto_pk_public_checksig(crypto_pk_t *env, char *to, size_t tolen, int crypto_pk_public_checksig(const crypto_pk_t *env, char *to, size_t tolen,
const char *from, size_t fromlen); const char *from, size_t fromlen);
int crypto_pk_public_checksig_digest(crypto_pk_t *env, const char *data, int crypto_pk_public_checksig_digest(crypto_pk_t *env, const char *data,
size_t datalen, const char *sig, size_t siglen); size_t datalen, const char *sig, size_t siglen);
int crypto_pk_private_sign(crypto_pk_t *env, char *to, size_t tolen, int crypto_pk_private_sign(const crypto_pk_t *env, char *to, size_t tolen,
const char *from, size_t fromlen); const char *from, size_t fromlen);
int crypto_pk_private_sign_digest(crypto_pk_t *env, char *to, size_t tolen, int crypto_pk_private_sign_digest(crypto_pk_t *env, char *to, size_t tolen,
const char *from, size_t fromlen); const char *from, size_t fromlen);

View File

@ -2023,6 +2023,9 @@ typedef struct {
curve25519_public_key_t *onion_curve25519_pkey; curve25519_public_key_t *onion_curve25519_pkey;
/** Certificate for ed25519 signing key */ /** Certificate for ed25519 signing key */
struct tor_cert_st *signing_key_cert; struct tor_cert_st *signing_key_cert;
/** What's the earliest expiration time on all the certs in this
* routerinfo? */
time_t cert_expiration_time;
char *platform; /**< What software/operating system is this OR using? */ char *platform; /**< What software/operating system is this OR using? */
@ -5043,6 +5046,8 @@ typedef enum was_router_added_t {
/* Router descriptor was rejected because it was older than /* Router descriptor was rejected because it was older than
* OLD_ROUTER_DESC_MAX_AGE. */ * OLD_ROUTER_DESC_MAX_AGE. */
ROUTER_WAS_TOO_OLD = -7, /* note contrast with 'NOT_NEW' */ ROUTER_WAS_TOO_OLD = -7, /* note contrast with 'NOT_NEW' */
/* DOCDOC */
ROUTER_CERTS_EXPIRED = -8
} was_router_added_t; } was_router_added_t;
/********************************* routerparse.c ************************/ /********************************* routerparse.c ************************/

View File

@ -2000,6 +2000,8 @@ router_rebuild_descriptor(int force)
} }
if (! (ri->cache_info.signed_descriptor_body = if (! (ri->cache_info.signed_descriptor_body =
router_dump_router_to_string(ri, get_server_identity_key(), router_dump_router_to_string(ri, get_server_identity_key(),
get_onion_key(),
get_current_curve25519_keypair(),
get_master_signing_keypair())) ) { get_master_signing_keypair())) ) {
log_warn(LD_BUG, "Couldn't generate router descriptor."); log_warn(LD_BUG, "Couldn't generate router descriptor.");
routerinfo_free(ri); routerinfo_free(ri);
@ -2306,7 +2308,9 @@ get_platform_str(char *platform, size_t len)
*/ */
char * char *
router_dump_router_to_string(routerinfo_t *router, router_dump_router_to_string(routerinfo_t *router,
crypto_pk_t *ident_key, const crypto_pk_t *ident_key,
const crypto_pk_t *tap_key,
const curve25519_keypair_t *ntor_keypair,
const ed25519_keypair_t *signing_keypair) const ed25519_keypair_t *signing_keypair)
{ {
char *address = NULL; char *address = NULL;
@ -2325,6 +2329,8 @@ router_dump_router_to_string(routerinfo_t *router,
char *output = NULL; char *output = NULL;
const int emit_ed_sigs = signing_keypair && router->signing_key_cert; const int emit_ed_sigs = signing_keypair && router->signing_key_cert;
char *ed_cert_line = NULL; char *ed_cert_line = NULL;
char *rsa_tap_cc_line = NULL;
char *ntor_cc_line = NULL;
/* Make sure the identity key matches the one in the routerinfo. */ /* Make sure the identity key matches the one in the routerinfo. */
if (!crypto_pk_eq_keys(ident_key, router->identity_pkey)) { if (!crypto_pk_eq_keys(ident_key, router->identity_pkey)) {
@ -2377,6 +2383,67 @@ router_dump_router_to_string(routerinfo_t *router,
goto err; goto err;
} }
/* Cross-certify with RSA key */
if (tap_key && router->signing_key_cert &&
router->signing_key_cert->signing_key_included) {
char buf[256];
int tap_cc_len = 0;
uint8_t *tap_cc =
make_tap_onion_key_crosscert(tap_key,
&router->signing_key_cert->signing_key,
router->identity_pkey,
&tap_cc_len);
if (!tap_cc) {
log_warn(LD_BUG,"make_tap_onion_key_crosscert failed!");
goto err;
}
if (base64_encode(buf, sizeof(buf), (const char*)tap_cc, tap_cc_len) < 0) {
log_warn(LD_BUG,"base64_encode(rsa_crosscert) failed!");
tor_free(tap_cc);
goto err;
}
tor_free(tap_cc);
tor_asprintf(&rsa_tap_cc_line,
"onion-key-crosscert\n"
"-----BEGIN CROSSCERT-----\n"
"%s"
"-----END CROSSCERT-----\n", buf);
}
/* Cross-certify with onion keys */
if (ntor_keypair && router->signing_key_cert &&
router->signing_key_cert->signing_key_included) {
int sign = 0;
char buf[256];
/* XXXX Base the expiration date on the actual onion key expiration time?*/
tor_cert_t *cert =
make_ntor_onion_key_crosscert(ntor_keypair,
&router->signing_key_cert->signing_key,
router->cache_info.published_on,
MIN_ONION_KEY_LIFETIME, &sign);
if (!cert) {
log_warn(LD_BUG,"make_ntor_onion_key_crosscert failed!");
goto err;
}
tor_assert(sign == 0 || sign == 1);
if (base64_encode(buf, sizeof(buf),
(const char*)cert->encoded, cert->encoded_len)<0) {
log_warn(LD_BUG,"base64_encode(ntor_crosscert) failed!");
tor_cert_free(cert);
goto err;
}
tor_cert_free(cert);
tor_asprintf(&ntor_cc_line,
"ntor-onion-key-crosscert %d\n"
"-----BEGIN ED25519 CERT-----\n"
"%s"
"-----END ED25519 CERT-----\n", sign, buf);
}
/* Encode the publication time. */ /* Encode the publication time. */
format_iso_time(published, router->cache_info.published_on); format_iso_time(published, router->cache_info.published_on);
@ -2426,6 +2493,7 @@ router_dump_router_to_string(routerinfo_t *router,
"%s%s%s%s" "%s%s%s%s"
"onion-key\n%s" "onion-key\n%s"
"signing-key\n%s" "signing-key\n%s"
"%s%s"
"%s%s%s%s", "%s%s%s%s",
router->nickname, router->nickname,
address, address,
@ -2446,6 +2514,8 @@ router_dump_router_to_string(routerinfo_t *router,
(options->DownloadExtraInfo || options->V3AuthoritativeDir) ? (options->DownloadExtraInfo || options->V3AuthoritativeDir) ?
"caches-extra-info\n" : "", "caches-extra-info\n" : "",
onion_pkey, identity_pkey, onion_pkey, identity_pkey,
rsa_tap_cc_line ? rsa_tap_cc_line : "",
ntor_cc_line ? ntor_cc_line : "",
family_line, family_line,
we_are_hibernating() ? "hibernating 1\n" : "", we_are_hibernating() ? "hibernating 1\n" : "",
options->HidServDirectoryV2 ? "hidden-service-dir\n" : "", options->HidServDirectoryV2 ? "hidden-service-dir\n" : "",

View File

@ -91,7 +91,9 @@ int router_is_me(const routerinfo_t *router);
int router_pick_published_address(const or_options_t *options, uint32_t *addr); int router_pick_published_address(const or_options_t *options, uint32_t *addr);
int router_rebuild_descriptor(int force); int router_rebuild_descriptor(int force);
char *router_dump_router_to_string(routerinfo_t *router, char *router_dump_router_to_string(routerinfo_t *router,
crypto_pk_t *ident_key, const crypto_pk_t *ident_key,
const crypto_pk_t *tap_key,
const curve25519_keypair_t *ntor_keypair,
const ed25519_keypair_t *signing_keypair); const ed25519_keypair_t *signing_keypair);
char *router_dump_exit_policy_to_string(const routerinfo_t *router, char *router_dump_exit_policy_to_string(const routerinfo_t *router,
int include_ipv4, int include_ipv4,

View File

@ -435,6 +435,93 @@ get_current_auth_key_cert(void)
return auth_key_cert; return auth_key_cert;
} }
/** Construct cross-certification for the master identity key with
* the ntor onion key. Store the sign of the corresponding ed25519 public key
* in *<b>sign_out</b>. */
tor_cert_t *
make_ntor_onion_key_crosscert(const curve25519_keypair_t *onion_key,
const ed25519_public_key_t *master_id_key, time_t now, time_t lifetime,
int *sign_out)
{
tor_cert_t *cert = NULL;
ed25519_keypair_t ed_onion_key;
if (ed25519_keypair_from_curve25519_keypair(&ed_onion_key, sign_out,
onion_key) < 0)
goto end;
cert = tor_cert_create(&ed_onion_key, CERT_TYPE_ONION_ID, master_id_key,
now, lifetime, 0);
end:
memwipe(&ed_onion_key, 0, sizeof(ed_onion_key));
return cert;
}
/** Construct and return an RSA signature for the TAP onion key to
* cross-certify the RSA and Ed25519 identity keys. Set <b>len_out</b> to its
* length. */
uint8_t *
make_tap_onion_key_crosscert(const crypto_pk_t *onion_key,
const ed25519_public_key_t *master_id_key,
const crypto_pk_t *rsa_id_key,
int *len_out)
{
uint8_t signature[PK_BYTES];
uint8_t signed_data[DIGEST_LEN + ED25519_PUBKEY_LEN];
*len_out = 0;
crypto_pk_get_digest(rsa_id_key, (char*)signed_data);
memcpy(signed_data + DIGEST_LEN, master_id_key->pubkey, ED25519_PUBKEY_LEN);
int r = crypto_pk_private_sign(onion_key,
(char*)signature, sizeof(signature),
(const char*)signed_data, sizeof(signed_data));
if (r < 0)
return NULL;
*len_out = r;
return tor_memdup(signature, r);
}
/** Check whether an RSA-TAP cross-certification is correct. Return 0 if it
* is, -1 if it isn't. */
int
check_tap_onion_key_crosscert(const uint8_t *crosscert,
int crosscert_len,
const crypto_pk_t *onion_pkey,
const ed25519_public_key_t *master_id_pkey,
const uint8_t *rsa_id_digest)
{
uint8_t *cc = tor_malloc(crypto_pk_keysize(onion_pkey));
int cc_len =
crypto_pk_public_checksig(onion_pkey,
(char*)cc,
crypto_pk_keysize(onion_pkey),
(const char*)crosscert,
crosscert_len);
if (cc_len < 0) {
goto err;
}
if (cc_len < DIGEST_LEN + ED25519_PUBKEY_LEN) {
log_warn(LD_DIR, "Short signature on cross-certification with TAP key");
goto err;
}
if (tor_memneq(cc, rsa_id_digest, DIGEST_LEN) ||
tor_memneq(cc + DIGEST_LEN, master_id_pkey->pubkey,
ED25519_PUBKEY_LEN)) {
log_warn(LD_DIR, "Incorrect cross-certification with TAP key");
goto err;
}
tor_free(cc);
return 0;
err:
tor_free(cc);
return -1;
}
void void
routerkeys_free_all(void) routerkeys_free_all(void)
{ {

View File

@ -37,6 +37,22 @@ const ed25519_keypair_t *get_current_auth_keypair(void);
const struct tor_cert_st *get_current_link_key_cert(void); const struct tor_cert_st *get_current_link_key_cert(void);
const struct tor_cert_st *get_current_auth_key_cert(void); const struct tor_cert_st *get_current_auth_key_cert(void);
struct tor_cert_st *make_ntor_onion_key_crosscert(
const curve25519_keypair_t *onion_key,
const ed25519_public_key_t *master_id_key,
time_t now, time_t lifetime,
int *sign_out);
uint8_t *make_tap_onion_key_crosscert(const crypto_pk_t *onion_key,
const ed25519_public_key_t *master_id_key,
const crypto_pk_t *rsa_id_key,
int *len_out);
int check_tap_onion_key_crosscert(const uint8_t *crosscert,
int crosscert_len,
const crypto_pk_t *onion_pkey,
const ed25519_public_key_t *master_id_pkey,
const uint8_t *rsa_id_digest);
int load_ed_keys(const or_options_t *options, time_t now); int load_ed_keys(const or_options_t *options, time_t now);
void routerkeys_free_all(void); void routerkeys_free_all(void);

View File

@ -3294,6 +3294,11 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg,
old_router = router_get_mutable_by_digest(id_digest); old_router = router_get_mutable_by_digest(id_digest);
/* Make sure that it isn't expired. */
if (router->cert_expiration_time < approx_time()) {
return ROUTER_CERTS_EXPIRED;
}
/* Make sure that we haven't already got this exact descriptor. */ /* Make sure that we haven't already got this exact descriptor. */
if (sdmap_get(routerlist->desc_digest_map, if (sdmap_get(routerlist->desc_digest_map,
router->cache_info.signed_descriptor_digest)) { router->cache_info.signed_descriptor_digest)) {

View File

@ -118,13 +118,15 @@ WRA_WAS_ADDED(was_router_added_t s) {
* - not in the consensus * - not in the consensus
* - neither in the consensus nor in any networkstatus document * - neither in the consensus nor in any networkstatus document
* - it was outdated. * - it was outdated.
* - its certificates were expired.
*/ */
static INLINE int WRA_WAS_OUTDATED(was_router_added_t s) static INLINE int WRA_WAS_OUTDATED(was_router_added_t s)
{ {
return (s == ROUTER_WAS_TOO_OLD || return (s == ROUTER_WAS_TOO_OLD ||
s == ROUTER_IS_ALREADY_KNOWN || s == ROUTER_IS_ALREADY_KNOWN ||
s == ROUTER_NOT_IN_CONSENSUS || s == ROUTER_NOT_IN_CONSENSUS ||
s == ROUTER_NOT_IN_CONSENSUS_OR_NETWORKSTATUS); s == ROUTER_NOT_IN_CONSENSUS_OR_NETWORKSTATUS ||
s == ROUTER_CERTS_EXPIRED);
} }
/** Return true iff the outcome code in <b>s</b> indicates that the descriptor /** Return true iff the outcome code in <b>s</b> indicates that the descriptor
* was flat-out rejected. */ * was flat-out rejected. */
@ -138,7 +140,8 @@ static INLINE int WRA_NEVER_DOWNLOADABLE(was_router_added_t s)
{ {
return (s == ROUTER_AUTHDIR_REJECTS || return (s == ROUTER_AUTHDIR_REJECTS ||
s == ROUTER_BAD_EI || s == ROUTER_BAD_EI ||
s == ROUTER_WAS_TOO_OLD); s == ROUTER_WAS_TOO_OLD ||
s == ROUTER_CERTS_EXPIRED);
} }
was_router_added_t router_add_to_routerlist(routerinfo_t *router, was_router_added_t router_add_to_routerlist(routerinfo_t *router,
const char **msg, const char **msg,

View File

@ -24,6 +24,7 @@
#include "microdesc.h" #include "microdesc.h"
#include "networkstatus.h" #include "networkstatus.h"
#include "rephist.h" #include "rephist.h"
#include "routerkeys.h"
#include "routerparse.h" #include "routerparse.h"
#include "entrynodes.h" #include "entrynodes.h"
#include "torcert.h" #include "torcert.h"
@ -87,6 +88,8 @@ typedef enum {
K_IPV6_POLICY, K_IPV6_POLICY,
K_ROUTER_SIG_ED25519, K_ROUTER_SIG_ED25519,
K_IDENTITY_ED25519, K_IDENTITY_ED25519,
K_ONION_KEY_CROSSCERT,
K_NTOR_ONION_KEY_CROSSCERT,
K_DIRREQ_END, K_DIRREQ_END,
K_DIRREQ_V2_IPS, K_DIRREQ_V2_IPS,
@ -299,6 +302,9 @@ static token_rule_t routerdesc_token_table[] = {
T01("hidden-service-dir", K_HIDDEN_SERVICE_DIR, NO_ARGS, NO_OBJ ), T01("hidden-service-dir", K_HIDDEN_SERVICE_DIR, NO_ARGS, NO_OBJ ),
T01("identity-ed25519", K_IDENTITY_ED25519, NO_ARGS, NEED_OBJ ), T01("identity-ed25519", K_IDENTITY_ED25519, NO_ARGS, NEED_OBJ ),
T01("router-sig-ed25519", K_ROUTER_SIG_ED25519, GE(1), NO_OBJ ), T01("router-sig-ed25519", K_ROUTER_SIG_ED25519, GE(1), NO_OBJ ),
T01("onion-key-crosscert", K_ONION_KEY_CROSSCERT, NO_ARGS, NEED_OBJ ),
T01("ntor-onion-key-crosscert", K_NTOR_ONION_KEY_CROSSCERT,
EQ(1), NEED_OBJ ),
T01("allow-single-hop-exits",K_ALLOW_SINGLE_HOP_EXITS, NO_ARGS, NO_OBJ ), T01("allow-single-hop-exits",K_ALLOW_SINGLE_HOP_EXITS, NO_ARGS, NO_OBJ ),
@ -648,7 +654,7 @@ router_get_extrainfo_hash(const char *s, size_t s_len, char *digest)
char * char *
router_get_dirobj_signature(const char *digest, router_get_dirobj_signature(const char *digest,
size_t digest_len, size_t digest_len,
crypto_pk_t *private_key) const crypto_pk_t *private_key)
{ {
char *signature; char *signature;
size_t i, keysize; size_t i, keysize;
@ -1116,6 +1122,7 @@ router_parse_entry_from_string(const char *s, const char *end,
size_t prepend_len = prepend_annotations ? strlen(prepend_annotations) : 0; size_t prepend_len = prepend_annotations ? strlen(prepend_annotations) : 0;
int ok = 1; int ok = 1;
memarea_t *area = NULL; memarea_t *area = NULL;
tor_cert_t *ntor_cc_cert = NULL;
/* Do not set this to '1' until we have parsed everything that we intend to /* Do not set this to '1' until we have parsed everything that we intend to
* parse that's covered by the hash. */ * parse that's covered by the hash. */
int can_dl_again = 0; int can_dl_again = 0;
@ -1191,6 +1198,7 @@ router_parse_entry_from_string(const char *s, const char *end,
tor_assert(tok->n_args >= 5); tor_assert(tok->n_args >= 5);
router = tor_malloc_zero(sizeof(routerinfo_t)); router = tor_malloc_zero(sizeof(routerinfo_t));
router->cert_expiration_time = TIME_MAX;
router->cache_info.routerlist_index = -1; router->cache_info.routerlist_index = -1;
router->cache_info.annotations_len = s-start_of_annotations + prepend_len; router->cache_info.annotations_len = s-start_of_annotations + prepend_len;
router->cache_info.signed_descriptor_len = end-s; router->cache_info.signed_descriptor_len = end-s;
@ -1313,16 +1321,30 @@ router_parse_entry_from_string(const char *s, const char *end,
tor_memdup(&k, sizeof(curve25519_public_key_t)); tor_memdup(&k, sizeof(curve25519_public_key_t));
} }
tok = find_by_keyword(tokens, K_SIGNING_KEY);
router->identity_pkey = tok->key;
tok->key = NULL; /* Prevent free */
if (crypto_pk_get_digest(router->identity_pkey,
router->cache_info.identity_digest)) {
log_warn(LD_DIR, "Couldn't calculate key digest"); goto err;
}
{ {
directory_token_t *ed_sig_tok, *ed_cert_tok; directory_token_t *ed_sig_tok, *ed_cert_tok, *cc_tap_tok, *cc_ntor_tok;
ed_sig_tok = find_opt_by_keyword(tokens, K_ROUTER_SIG_ED25519); ed_sig_tok = find_opt_by_keyword(tokens, K_ROUTER_SIG_ED25519);
ed_cert_tok = find_opt_by_keyword(tokens, K_IDENTITY_ED25519); ed_cert_tok = find_opt_by_keyword(tokens, K_IDENTITY_ED25519);
if (!ed_sig_tok != !ed_cert_tok) { cc_tap_tok = find_opt_by_keyword(tokens, K_ONION_KEY_CROSSCERT);
log_warn(LD_DIR, "Router descriptor with only partial ed25519 support"); cc_ntor_tok = find_opt_by_keyword(tokens, K_NTOR_ONION_KEY_CROSSCERT);
int n_ed_toks = !!ed_sig_tok + !!ed_cert_tok +
!!cc_tap_tok + !!cc_ntor_tok;
if ((n_ed_toks != 0 && n_ed_toks != 4) ||
(n_ed_toks == 4 && !router->onion_curve25519_pkey)) {
log_warn(LD_DIR, "Router descriptor with only partial ed25519/"
"cross-certification support");
goto err; goto err;
} }
if (ed_sig_tok) { if (ed_sig_tok) {
tor_assert(ed_cert_tok); tor_assert(ed_cert_tok && cc_tap_tok && cc_ntor_tok);
if (ed_cert_tok != smartlist_get(tokens, 0) && if (ed_cert_tok != smartlist_get(tokens, 0) &&
ed_cert_tok != smartlist_get(tokens, 1)) { ed_cert_tok != smartlist_get(tokens, 1)) {
log_warn(LD_DIR, "Ed25519 certificate in wrong position"); log_warn(LD_DIR, "Ed25519 certificate in wrong position");
@ -1336,10 +1358,25 @@ router_parse_entry_from_string(const char *s, const char *end,
log_warn(LD_DIR, "Wrong object type on identity-ed25519 in decriptor"); log_warn(LD_DIR, "Wrong object type on identity-ed25519 in decriptor");
goto err; goto err;
} }
if (strcmp(cc_ntor_tok->object_type, "ED25519 CERT")) {
log_warn(LD_DIR, "Wrong object type on ntor-onion-key-crosscert "
"in decriptor");
goto err;
}
if (strcmp(cc_tap_tok->object_type, "CROSSCERT")) {
log_warn(LD_DIR, "Wrong object type on onion-key-crosscert "
"in decriptor");
goto err;
}
if (strcmp(cc_ntor_tok->args[0], "0") &&
strcmp(cc_ntor_tok->args[0], "1")) {
log_warn(LD_DIR, "Bad sign bit on ntor-onion-key-crosscert");
goto err;
}
int ntor_cc_sign_bit = !strcmp(cc_ntor_tok->args[0], "1");
uint8_t d256[DIGEST256_LEN]; uint8_t d256[DIGEST256_LEN];
const char *signed_start, *signed_end; const char *signed_start, *signed_end;
tor_cert_t *cert = tor_cert_parse( tor_cert_t *cert = tor_cert_parse(
(const uint8_t*)ed_cert_tok->object_body, (const uint8_t*)ed_cert_tok->object_body,
ed_cert_tok->object_size); ed_cert_tok->object_size);
@ -1347,13 +1384,33 @@ router_parse_entry_from_string(const char *s, const char *end,
log_warn(LD_DIR, "Couldn't parse ed25519 cert"); log_warn(LD_DIR, "Couldn't parse ed25519 cert");
goto err; goto err;
} }
router->signing_key_cert = cert; router->signing_key_cert = cert; /* makes sure it gets freed. */
if (cert->cert_type != CERT_TYPE_ID_SIGNING || if (cert->cert_type != CERT_TYPE_ID_SIGNING ||
! cert->signing_key_included) { ! cert->signing_key_included) {
log_warn(LD_DIR, "Invalid form for ed25519 cert"); log_warn(LD_DIR, "Invalid form for ed25519 cert");
goto err; goto err;
} }
ntor_cc_cert = tor_cert_parse((const uint8_t*)cc_ntor_tok->object_body,
cc_ntor_tok->object_size);
if (!cc_ntor_tok) {
log_warn(LD_DIR, "Couldn't parse ntor-onion-key-crosscert cert");
goto err;
}
if (ntor_cc_cert->cert_type != CERT_TYPE_ONION_ID ||
! ed25519_pubkey_eq(&ntor_cc_cert->signed_key, &cert->signing_key)) {
log_warn(LD_DIR, "Invalid contents for ntor-onion-key-crosscert cert");
goto err;
}
ed25519_public_key_t ntor_cc_pk;
if (ed25519_public_key_from_curve25519_public_key(&ntor_cc_pk,
router->onion_curve25519_pkey,
ntor_cc_sign_bit)<0) {
log_warn(LD_DIR, "Error converting onion key to ed25519");
goto err;
}
if (router_get_hash_impl_helper(s, end-s, "router ", if (router_get_hash_impl_helper(s, end-s, "router ",
"\nrouter-sig-ed25519", "\nrouter-sig-ed25519",
' ', &signed_start, &signed_end) < 0) { ' ', &signed_start, &signed_end) < 0) {
@ -1367,38 +1424,48 @@ router_parse_entry_from_string(const char *s, const char *end,
crypto_digest_get_digest(d, (char*)d256, sizeof(d256)); crypto_digest_get_digest(d, (char*)d256, sizeof(d256));
crypto_digest_free(d); crypto_digest_free(d);
ed25519_checkable_t check[2]; ed25519_checkable_t check[3];
int check_ok[2]; int check_ok[3];
if (tor_cert_get_checkable_sig(&check[0], cert, NULL) < 0) { if (tor_cert_get_checkable_sig(&check[0], cert, NULL) < 0) {
log_err(LD_BUG, "Couldn't create 'checkable' for cert."); log_err(LD_BUG, "Couldn't create 'checkable' for cert.");
goto err; goto err;
} }
if (ed25519_signature_from_base64(&check[1].signature, if (tor_cert_get_checkable_sig(&check[1],
ntor_cc_cert, &ntor_cc_pk) < 0) {
log_err(LD_BUG, "Couldn't create 'checkable' for ntor_cc_cert.");
goto err;
}
if (ed25519_signature_from_base64(&check[2].signature,
ed_sig_tok->args[0])<0) { ed_sig_tok->args[0])<0) {
log_warn(LD_DIR, "Couldn't decode ed25519 signature"); log_warn(LD_DIR, "Couldn't decode ed25519 signature");
goto err; goto err;
} }
check[1].pubkey = &cert->signed_key; check[2].pubkey = &cert->signed_key;
check[1].msg = d256; check[2].msg = d256;
check[1].len = DIGEST256_LEN; check[2].len = DIGEST256_LEN;
if (ed25519_checksig_batch(check_ok, check, 2) < 0) { if (ed25519_checksig_batch(check_ok, check, 3) < 0) {
log_warn(LD_DIR, "Incorrect ed25519 signatures"); log_warn(LD_DIR, "Incorrect ed25519 signature(s)");
goto err; goto err;
} }
if (cert->valid_until < time(NULL)) {
log_warn(LD_DIR, "Expired ed25519 certificate in router descriptor"); if (check_tap_onion_key_crosscert(
(const uint8_t*)cc_tap_tok->object_body,
(int)cc_tap_tok->object_size,
router->onion_pkey,
&cert->signing_key,
(const uint8_t*)router->cache_info.identity_digest)<0) {
log_warn(LD_DIR, "Incorrect TAP cross-verification");
goto err; goto err;
} }
}
}
tok = find_by_keyword(tokens, K_SIGNING_KEY); /* We check this before adding it to the routerlist. */
router->identity_pkey = tok->key; if (cert->valid_until < ntor_cc_cert->valid_until)
tok->key = NULL; /* Prevent free */ router->cert_expiration_time = cert->valid_until;
if (crypto_pk_get_digest(router->identity_pkey, else
router->cache_info.identity_digest)) { router->cert_expiration_time = ntor_cc_cert->valid_until;
log_warn(LD_DIR, "Couldn't calculate key digest"); goto err; }
} }
if ((tok = find_opt_by_keyword(tokens, K_FINGERPRINT))) { if ((tok = find_opt_by_keyword(tokens, K_FINGERPRINT))) {
@ -1527,6 +1594,7 @@ router_parse_entry_from_string(const char *s, const char *end,
routerinfo_free(router); routerinfo_free(router);
router = NULL; router = NULL;
done: done:
tor_cert_free(ntor_cc_cert);
if (tokens) { if (tokens) {
SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
smartlist_free(tokens); smartlist_free(tokens);

View File

@ -19,7 +19,7 @@ int router_get_extrainfo_hash(const char *s, size_t s_len, char *digest);
#define DIROBJ_MAX_SIG_LEN 256 #define DIROBJ_MAX_SIG_LEN 256
char *router_get_dirobj_signature(const char *digest, char *router_get_dirobj_signature(const char *digest,
size_t digest_len, size_t digest_len,
crypto_pk_t *private_key); const crypto_pk_t *private_key);
int router_append_dirobj_signature(char *buf, size_t buf_len, int router_append_dirobj_signature(char *buf, size_t buf_len,
const char *digest, const char *digest,
size_t digest_len, size_t digest_len,

View File

@ -11,6 +11,7 @@
#define CERT_TYPE_ID_SIGNING 0x04 #define CERT_TYPE_ID_SIGNING 0x04
#define CERT_TYPE_SIGNING_LINK 0x05 #define CERT_TYPE_SIGNING_LINK 0x05
#define CERT_TYPE_SIGNING_AUTH 0x06 #define CERT_TYPE_SIGNING_AUTH 0x06
#define CERT_TYPE_ONION_ID 0x0A
#define CERT_FLAG_INCLUDE_SIGNING_KEY 0x1 #define CERT_FLAG_INCLUDE_SIGNING_KEY 0x1

View File

@ -21,6 +21,7 @@
#include "hibernate.h" #include "hibernate.h"
#include "networkstatus.h" #include "networkstatus.h"
#include "router.h" #include "router.h"
#include "routerkeys.h"
#include "routerlist.h" #include "routerlist.h"
#include "routerparse.h" #include "routerparse.h"
#include "test.h" #include "test.h"
@ -89,6 +90,8 @@ test_dir_formats(void *arg)
routerinfo_t *rp1 = NULL, *rp2 = NULL; routerinfo_t *rp1 = NULL, *rp2 = NULL;
addr_policy_t *ex1, *ex2; addr_policy_t *ex1, *ex2;
routerlist_t *dir1 = NULL, *dir2 = NULL; routerlist_t *dir1 = NULL, *dir2 = NULL;
uint8_t *rsa_cc = NULL;
tor_cert_t *ntor_cc = NULL;
or_options_t *options = get_options_mutable(); or_options_t *options = get_options_mutable();
const addr_policy_t *p; const addr_policy_t *p;
time_t now = time(NULL); time_t now = time(NULL);
@ -152,8 +155,10 @@ test_dir_formats(void *arg)
r2->dir_port = 0; r2->dir_port = 0;
r2->onion_pkey = crypto_pk_dup_key(pk2); r2->onion_pkey = crypto_pk_dup_key(pk2);
r2->onion_curve25519_pkey = tor_malloc_zero(sizeof(curve25519_public_key_t)); r2->onion_curve25519_pkey = tor_malloc_zero(sizeof(curve25519_public_key_t));
curve25519_public_from_base64(r2->onion_curve25519_pkey, curve25519_keypair_t r2_onion_keypair;
"skyinAnvardNostarsNomoonNowindormistsorsnow"); curve25519_keypair_generate(&r2_onion_keypair, 0);
r2->onion_curve25519_pkey = tor_memdup(&r2_onion_keypair.pubkey,
sizeof(curve25519_public_key_t));
r2->identity_pkey = crypto_pk_dup_key(pk1); r2->identity_pkey = crypto_pk_dup_key(pk1);
r2->bandwidthrate = r2->bandwidthburst = r2->bandwidthcapacity = 3000; r2->bandwidthrate = r2->bandwidthburst = r2->bandwidthcapacity = 3000;
r2->exit_policy = smartlist_new(); r2->exit_policy = smartlist_new();
@ -169,7 +174,7 @@ test_dir_formats(void *arg)
/* XXXX025 router_dump_to_string should really take this from ri.*/ /* XXXX025 router_dump_to_string should really take this from ri.*/
options->ContactInfo = tor_strdup("Magri White " options->ContactInfo = tor_strdup("Magri White "
"<magri@elsewhere.example.com>"); "<magri@elsewhere.example.com>");
buf = router_dump_router_to_string(r1, pk2, NULL); buf = router_dump_router_to_string(r1, pk2, NULL, NULL, NULL);
tor_free(options->ContactInfo); tor_free(options->ContactInfo);
tt_assert(buf); tt_assert(buf);
@ -202,7 +207,7 @@ test_dir_formats(void *arg)
tt_str_op(buf,OP_EQ, buf2); tt_str_op(buf,OP_EQ, buf2);
tor_free(buf); tor_free(buf);
buf = router_dump_router_to_string(r1, pk2, NULL); buf = router_dump_router_to_string(r1, pk2, NULL, NULL, NULL);
tt_assert(buf); tt_assert(buf);
cp = buf; cp = buf;
rp1 = router_parse_entry_from_string((const char*)cp,NULL,1,0,NULL,NULL); rp1 = router_parse_entry_from_string((const char*)cp,NULL,1,0,NULL,NULL);
@ -238,20 +243,49 @@ test_dir_formats(void *arg)
strlcat(buf2, pk2_str, sizeof(buf2)); strlcat(buf2, pk2_str, sizeof(buf2));
strlcat(buf2, "signing-key\n", sizeof(buf2)); strlcat(buf2, "signing-key\n", sizeof(buf2));
strlcat(buf2, pk1_str, sizeof(buf2)); strlcat(buf2, pk1_str, sizeof(buf2));
int rsa_cc_len;
rsa_cc = make_tap_onion_key_crosscert(pk2,
&kp1.pubkey,
pk1,
&rsa_cc_len);
tt_assert(rsa_cc);
base64_encode(cert_buf, sizeof(cert_buf), (char*)rsa_cc, rsa_cc_len);
strlcat(buf2, "onion-key-crosscert\n"
"-----BEGIN CROSSCERT-----\n", sizeof(buf2));
strlcat(buf2, cert_buf, sizeof(buf2));
strlcat(buf2, "-----END CROSSCERT-----\n", sizeof(buf2));
int ntor_cc_sign;
ntor_cc = make_ntor_onion_key_crosscert(&r2_onion_keypair,
&kp1.pubkey,
r2->cache_info.published_on,
MIN_ONION_KEY_LIFETIME,
&ntor_cc_sign);
tt_assert(ntor_cc);
base64_encode(cert_buf, sizeof(cert_buf),
(char*)ntor_cc->encoded, ntor_cc->encoded_len);
tor_snprintf(buf2+strlen(buf2), sizeof(buf2)-strlen(buf2),
"ntor-onion-key-crosscert %d\n"
"-----BEGIN ED25519 CERT-----\n"
"%s"
"-----END ED25519 CERT-----\n", ntor_cc_sign, cert_buf);
strlcat(buf2, "hidden-service-dir\n", sizeof(buf2)); strlcat(buf2, "hidden-service-dir\n", sizeof(buf2));
strlcat(buf2, "ntor-onion-key " strlcat(buf2, "ntor-onion-key ", sizeof(buf2));
"skyinAnvardNostarsNomoonNowindormistsorsnow=\n", sizeof(buf2)); base64_encode(cert_buf, sizeof(cert_buf),
(const char*)r2_onion_keypair.pubkey.public_key, 32);
strlcat(buf2, cert_buf, sizeof(buf2));
strlcat(buf2, "accept *:80\nreject 18.0.0.0/8:24\n", sizeof(buf2)); strlcat(buf2, "accept *:80\nreject 18.0.0.0/8:24\n", sizeof(buf2));
strlcat(buf2, "router-sig-ed25519 ", sizeof(buf2)); strlcat(buf2, "router-sig-ed25519 ", sizeof(buf2));
buf = router_dump_router_to_string(r2, pk1, &kp2); buf = router_dump_router_to_string(r2, pk1, pk2, &r2_onion_keypair, &kp2);
tt_assert(buf); tt_assert(buf);
buf[strlen(buf2)] = '\0'; /* Don't compare the sig; it's never the same buf[strlen(buf2)] = '\0'; /* Don't compare the sig; it's never the same
* twice */ * twice */
tt_str_op(buf, OP_EQ, buf2); tt_str_op(buf, OP_EQ, buf2);
tor_free(buf); tor_free(buf);
buf = router_dump_router_to_string(r2, pk1, NULL); buf = router_dump_router_to_string(r2, pk1, NULL, NULL, NULL);
cp = buf; cp = buf;
rp2 = router_parse_entry_from_string((const char*)cp,NULL,1,0,NULL,NULL); rp2 = router_parse_entry_from_string((const char*)cp,NULL,1,0,NULL,NULL);
tt_assert(rp2); tt_assert(rp2);
@ -304,6 +338,7 @@ test_dir_formats(void *arg)
if (rp2) if (rp2)
routerinfo_free(rp2); routerinfo_free(rp2);
tor_free(rsa_cc);
tor_free(buf); tor_free(buf);
tor_free(pk1_str); tor_free(pk1_str);
tor_free(pk2_str); tor_free(pk2_str);
@ -1418,6 +1453,7 @@ generate_ri_from_rs(const vote_routerstatus_t *vrs)
static time_t published = 0; static time_t published = 0;
r = tor_malloc_zero(sizeof(routerinfo_t)); r = tor_malloc_zero(sizeof(routerinfo_t));
r->cert_expiration_time = TIME_MAX;
memcpy(r->cache_info.identity_digest, rs->identity_digest, DIGEST_LEN); memcpy(r->cache_info.identity_digest, rs->identity_digest, DIGEST_LEN);
memcpy(r->cache_info.signed_descriptor_digest, rs->descriptor_digest, memcpy(r->cache_info.signed_descriptor_digest, rs->descriptor_digest,
DIGEST_LEN); DIGEST_LEN);

View File

@ -514,6 +514,74 @@ test_routerkeys_ed_keys_init_all(void *arg)
routerkeys_free_all(); routerkeys_free_all();
} }
static void
test_routerkeys_cross_certify_ntor(void *args)
{
(void) args;
tor_cert_t *cert = NULL;
curve25519_keypair_t onion_keys;
ed25519_public_key_t master_key;
ed25519_public_key_t onion_check_key;
time_t now = time(NULL);
int sign;
tt_int_op(0, ==, ed25519_public_from_base64(&master_key,
"IamwritingthesetestsOnARainyAfternoonin2014"));
tt_int_op(0, ==, curve25519_keypair_generate(&onion_keys, 0));
cert = make_ntor_onion_key_crosscert(&onion_keys,
&master_key,
now, 10000,
&sign);
tt_assert(cert);
tt_assert(sign == 0 || sign == 1);
tt_int_op(cert->cert_type, ==, CERT_TYPE_ONION_ID);
tt_int_op(1, ==, ed25519_pubkey_eq(&cert->signed_key, &master_key));
tt_int_op(0, ==, ed25519_public_key_from_curve25519_public_key(
&onion_check_key, &onion_keys.pubkey, sign));
tt_int_op(0, ==, tor_cert_checksig(cert, &onion_check_key, now));
done:
tor_cert_free(cert);
}
static void
test_routerkeys_cross_certify_tap(void *args)
{
(void)args;
uint8_t *cc = NULL;
int cc_len;
ed25519_public_key_t master_key;
crypto_pk_t *onion_key = pk_generate(2), *id_key = pk_generate(1);
char digest[20];
char buf[128];
int n;
tt_int_op(0, ==, ed25519_public_from_base64(&master_key,
"IAlreadyWroteTestsForRouterdescsUsingTheseX"));
cc = make_tap_onion_key_crosscert(onion_key,
&master_key,
id_key, &cc_len);
tt_assert(cc);
tt_assert(cc_len);
n = crypto_pk_public_checksig(onion_key, buf, sizeof(buf),
(char*)cc, cc_len);
tt_int_op(n,>,0);
tt_int_op(n,==,52);
crypto_pk_get_digest(id_key, digest);
tt_mem_op(buf,==,digest,20);
tt_mem_op(buf+20,==,master_key.pubkey,32);
tt_int_op(0, ==, check_tap_onion_key_crosscert(cc, cc_len,
onion_key, &master_key, (uint8_t*)digest));
done:
tor_free(cc);
}
#define TEST(name, flags) \ #define TEST(name, flags) \
{ #name , test_routerkeys_ ## name, (flags), NULL, NULL } { #name , test_routerkeys_ ## name, (flags), NULL, NULL }
@ -524,6 +592,8 @@ struct testcase_t routerkeys_tests[] = {
TEST(ed_key_init_basic, TT_FORK), TEST(ed_key_init_basic, TT_FORK),
TEST(ed_key_init_split, TT_FORK), TEST(ed_key_init_split, TT_FORK),
TEST(ed_keys_init_all, TT_FORK), TEST(ed_keys_init_all, TT_FORK),
TEST(cross_certify_ntor, 0),
TEST(cross_certify_tap, 0),
END_OF_TESTCASES END_OF_TESTCASES
}; };