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.
This commit is contained in:
Nick Mathewson 2016-08-10 14:19:09 -04:00
parent 348b90a915
commit e3c8253721
3 changed files with 171 additions and 1 deletions

View File

@ -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, \

View File

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

View File

@ -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
};