Commit e3c82537 authored by Nick Mathewson's avatar Nick Mathewson 🦀
Browse files

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.
parent 348b90a9
Loading
Loading
Loading
Loading
+104 −1
Original line number Diff line number Diff line
@@ -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,                               \
+5 −0
Original line number Diff line number Diff line
@@ -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);
+62 −0
Original line number Diff line number Diff line
@@ -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
};