commit e3c825372180be00aff9c8e5cde60ea36d141f8c Author: Nick Mathewson nickm@torproject.org Date: Wed Aug 10 14:19:09 2016 -0400
Add function to check RSA->Ed cross-certifications
Also, adjust signing approach to more closely match the signing scheme in the proposal.
(The format doesn't quite match the format in the proposal, since RSA signatures aren't fixed-length.)
Closes 19020. --- src/or/torcert.c | 105 ++++++++++++++++++++++++++++++++++++++++++++- src/or/torcert.h | 5 +++ src/test/test_routerkeys.c | 62 ++++++++++++++++++++++++++ 3 files changed, 171 insertions(+), 1 deletion(-)
diff --git a/src/or/torcert.c b/src/or/torcert.c index 2f0cc53..94382f6 100644 --- a/src/or/torcert.c +++ b/src/or/torcert.c @@ -257,6 +257,8 @@ tor_cert_opt_eq(const tor_cert_t *cert1, const tor_cert_t *cert2) return tor_cert_eq(cert1, cert2); }
+#define RSA_ED_CROSSCERT_PREFIX "Tor TLS RSA/Ed25519 cross-certificate" + /** Create new cross-certification object to certify <b>ed_key</b> as the * master ed25519 identity key for the RSA identity key <b>rsa_key</b>. * Allocates and stores the encoded certificate in *<b>cert</b>, and returns @@ -281,11 +283,21 @@ tor_make_rsa_ed25519_crosscert(const ed25519_public_key_t *ed_key, ssize_t sz = rsa_ed_crosscert_encode(res, alloc_sz, cc); tor_assert(sz > 0 && sz <= alloc_sz);
+ crypto_digest_t *d = crypto_digest256_new(DIGEST_SHA256); + crypto_digest_add_bytes(d, RSA_ED_CROSSCERT_PREFIX, + strlen(RSA_ED_CROSSCERT_PREFIX)); + const int signed_part_len = 32 + 4; + crypto_digest_add_bytes(d, (char*)res, signed_part_len); + + uint8_t digest[DIGEST256_LEN]; + crypto_digest_get_digest(d, (char*)digest, sizeof(digest)); + crypto_digest_free(d); + int siglen = crypto_pk_private_sign(rsa_key, (char*)rsa_ed_crosscert_getarray_sig(cc), rsa_ed_crosscert_getlen_sig(cc), - (char*)res, signed_part_len); + (char*)digest, sizeof(digest)); tor_assert(siglen > 0 && siglen <= (int)crypto_pk_keysize(rsa_key)); tor_assert(siglen <= UINT8_MAX); cc->sig_len = siglen; @@ -297,6 +309,96 @@ tor_make_rsa_ed25519_crosscert(const ed25519_public_key_t *ed_key, return sz; }
+/** + * Check whether the <b>crosscert_len</b> byte certificate in <b>crosscert</b> + * is in fact a correct cross-certification of <b>master_key</b> using + * the RSA key <b>rsa_id_key</b>. + * + * Also reject the certificate if it expired before + * <b>reject_if_expired_before</b>. + * + * Return 0 on success, negative on failure. + */ +int +rsa_ed25519_crosscert_check(const uint8_t *crosscert, + const size_t crosscert_len, + const crypto_pk_t *rsa_id_key, + const ed25519_public_key_t *master_key, + const time_t reject_if_expired_before) +{ + rsa_ed_crosscert_t *cc = NULL; + int rv; + +#define ERR(code, s) \ + do { \ + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, \ + "Received a bad RSA->Ed25519 crosscert: %s", \ + (s)); \ + rv = (code); \ + goto err; \ + } while (0) + + if (BUG(crypto_pk_keysize(rsa_id_key) > PK_BYTES)) + return -1; + + if (BUG(!crosscert)) + return -1; + + ssize_t parsed_len = rsa_ed_crosscert_parse(&cc, crosscert, crosscert_len); + if (parsed_len < 0 || crosscert_len != (size_t)parsed_len) { + ERR(-2, "Unparseable or overlong crosscert"); + } + + if (tor_memneq(rsa_ed_crosscert_getarray_ed_key(cc), + master_key->pubkey, + ED25519_PUBKEY_LEN)) { + ERR(-3, "Crosscert did not match Ed25519 key"); + } + + const uint32_t expiration_date = rsa_ed_crosscert_get_expiration(cc); + const uint64_t expiration_time = expiration_date * 3600; + + if (reject_if_expired_before < 0 || + expiration_time < (uint64_t)reject_if_expired_before) { + ERR(-4, "Crosscert is expired"); + } + + const uint8_t *eos = rsa_ed_crosscert_get_end_of_signed(cc); + const uint8_t *sig = rsa_ed_crosscert_getarray_sig(cc); + const uint8_t siglen = rsa_ed_crosscert_get_sig_len(cc); + tor_assert(eos >= crosscert); + tor_assert((size_t)(eos - crosscert) <= crosscert_len); + tor_assert(siglen == rsa_ed_crosscert_getlen_sig(cc)); + + /* Compute the digest */ + uint8_t digest[DIGEST256_LEN]; + crypto_digest_t *d = crypto_digest256_new(DIGEST_SHA256); + crypto_digest_add_bytes(d, RSA_ED_CROSSCERT_PREFIX, + strlen(RSA_ED_CROSSCERT_PREFIX)); + crypto_digest_add_bytes(d, (char*)crosscert, eos-crosscert); + crypto_digest_get_digest(d, (char*)digest, sizeof(digest)); + crypto_digest_free(d); + + /* Now check the signature */ + uint8_t signed_[PK_BYTES]; + int signed_len = crypto_pk_public_checksig(rsa_id_key, + (char*)signed_, sizeof(signed_), + (char*)sig, siglen); + if (signed_len < DIGEST256_LEN) { + ERR(-5, "Bad signature, or length of signed data not as expected"); + } + + if (tor_memneq(digest, signed_, DIGEST256_LEN)) { + ERR(-6, "The signature was good, but it didn't match the data"); + } + + rv = 0; + err: + rsa_ed_crosscert_free(cc); + return rv; +} + +/** Construct and return a new empty or_handshake_certs object */ or_handshake_certs_t * or_handshake_certs_new(void) { @@ -317,6 +419,7 @@ or_handshake_certs_free(or_handshake_certs_t *certs) tor_free(certs); }
+#undef ERR #define ERR(s) \ do { \ log_fn(severity, LD_PROTOCOL, \ diff --git a/src/or/torcert.h b/src/or/torcert.h index 0420b41..39439d9 100644 --- a/src/or/torcert.h +++ b/src/or/torcert.h @@ -71,6 +71,11 @@ ssize_t tor_make_rsa_ed25519_crosscert(const ed25519_public_key_t *ed_key, const crypto_pk_t *rsa_key, time_t expires, uint8_t **cert); +int rsa_ed25519_crosscert_check(const uint8_t *crosscert, + const size_t crosscert_len, + const crypto_pk_t *rsa_id_key, + const ed25519_public_key_t *master_key, + const time_t reject_if_expired_before);
or_handshake_certs_t *or_handshake_certs_new(void); void or_handshake_certs_free(or_handshake_certs_t *certs); diff --git a/src/test/test_routerkeys.c b/src/test/test_routerkeys.c index 24b0da1..56055a3 100644 --- a/src/test/test_routerkeys.c +++ b/src/test/test_routerkeys.c @@ -614,6 +614,67 @@ test_routerkeys_cross_certify_tap(void *args) crypto_pk_free(onion_key); }
+ +static void +test_routerkeys_rsa_ed_crosscert(void *arg) +{ + (void)arg; + ed25519_public_key_t ed; + crypto_pk_t *rsa = pk_generate(2); + + uint8_t *cc = NULL; + ssize_t cc_len; + time_t expires_in = 1470846177; + + tt_int_op(0, OP_EQ, ed25519_public_from_base64(&ed, + "ThisStringCanContainAnythingSoNoKeyHereNowX")); + cc_len = tor_make_rsa_ed25519_crosscert(&ed, rsa, expires_in, &cc); + + tt_int_op(cc_len, OP_GT, 0); + tt_int_op(cc_len, OP_GT, 37); /* key, expires, siglen */ + tt_mem_op(cc, OP_EQ, ed.pubkey, 32); + time_t expires_out = 3600 * ntohl(get_uint32(cc+32)); + tt_int_op(expires_out, OP_GE, expires_in); + tt_int_op(expires_out, OP_LE, expires_in + 3600); + + tt_int_op(cc_len, OP_EQ, 37 + get_uint8(cc+36)); + + tt_int_op(0, OP_EQ, rsa_ed25519_crosscert_check(cc, cc_len, rsa, &ed, + expires_in - 10)); + + /* Now try after it has expired */ + tt_int_op(-4, OP_EQ, rsa_ed25519_crosscert_check(cc, cc_len, rsa, &ed, + expires_out + 1)); + + /* Truncated object */ + tt_int_op(-2, OP_EQ, rsa_ed25519_crosscert_check(cc, cc_len - 2, rsa, &ed, + expires_in - 10)); + + /* Key not as expected */ + cc[0] ^= 3; + tt_int_op(-3, OP_EQ, rsa_ed25519_crosscert_check(cc, cc_len, rsa, &ed, + expires_in - 10)); + cc[0] ^= 3; + + /* Bad signature */ + cc[40] ^= 3; + tt_int_op(-5, OP_EQ, rsa_ed25519_crosscert_check(cc, cc_len, rsa, &ed, + expires_in - 10)); + cc[40] ^= 3; + + /* Signature of wrong data */ + cc[0] ^= 3; + ed.pubkey[0] ^= 3; + tt_int_op(-6, OP_EQ, rsa_ed25519_crosscert_check(cc, cc_len, rsa, &ed, + expires_in - 10)); + cc[0] ^= 3; + ed.pubkey[0] ^= 3; + + done: + crypto_pk_free(rsa); + tor_free(cc); +} + #define TEST(name, flags) \ { #name , test_routerkeys_ ## name, (flags), NULL, NULL }
@@ -626,6 +687,7 @@ struct testcase_t routerkeys_tests[] = { TEST(ed_keys_init_all, TT_FORK), TEST(cross_certify_ntor, 0), TEST(cross_certify_tap, 0), + TEST(rsa_ed_crosscert, 0), END_OF_TESTCASES };
tor-commits@lists.torproject.org