From e047f7f8652d9c67ed96d4ff6f02fa7e23333c54 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 6 Nov 2007 18:00:07 +0000 Subject: [PATCH] r16455@catbus: nickm | 2007-11-06 12:48:00 -0500 Parse CERT cells and act correctly when we get them. svn:r12396 --- doc/TODO | 10 +-- src/common/crypto.c | 11 +++ src/common/crypto.h | 1 + src/common/tortls.c | 154 ++++++++++++++++++++++++++++++++++++----- src/common/tortls.h | 6 ++ src/or/command.c | 45 ++++++++---- src/or/connection_or.c | 20 +++--- 7 files changed, 202 insertions(+), 45 deletions(-) diff --git a/doc/TODO b/doc/TODO index e19a1ad2db..93011a2ed1 100644 --- a/doc/TODO +++ b/doc/TODO @@ -34,16 +34,16 @@ Things we'd like to do in 0.2.0.x: o Add parse logic o Make CERT variable. o Make VERSIONS variable. - - CERT cells - - functions to parse x509 certs - - functions to validate a single x509 cert against a TLS connection - - functions to validate a chain of x509 certs, and extract a PK. + o CERT cells + o functions to parse x509 certs + o functions to validate a single x509 cert against a TLS connection + o functions to validate a chain of x509 certs, and extract a PK. o function to encode x509 certs o Parse CERT cells o Generate CERT cells o Keep copies of X509 certs around, not necessarily associated with connection. - - LINK_AUTH cells + . LINK_AUTH cells o Code to generate o Remember certificate digests from TLS o Code to parse and check diff --git a/src/common/crypto.c b/src/common/crypto.c index 4289f48395..c4a06c7c11 100644 --- a/src/common/crypto.c +++ b/src/common/crypto.c @@ -246,6 +246,17 @@ _crypto_new_pk_env_rsa(RSA *rsa) return env; } +/** used by tortls.c: wrap the RSA from an evp_pkey in a crypto_pk_env_t. + * returns NULL if this isn't an RSA key. */ +crypto_pk_env_t * +_crypto_new_pk_env_evp_pkey(EVP_PKEY *pkey) +{ + RSA *rsa; + if (!(rsa = EVP_PKEY_get1_RSA(pkey))) + return NULL; + return _crypto_new_pk_env_rsa(rsa); +} + /** used by tortls.c: get an equivalent EVP_PKEY* for a crypto_pk_env_t. Iff * private is set, include the private-key portion of the key. */ EVP_PKEY * diff --git a/src/common/crypto.h b/src/common/crypto.h index 610ea460d7..3f85295145 100644 --- a/src/common/crypto.h +++ b/src/common/crypto.h @@ -195,6 +195,7 @@ struct rsa_st; struct evp_pkey_st; struct dh_st; crypto_pk_env_t *_crypto_new_pk_env_rsa(struct rsa_st *rsa); +crypto_pk_env_t *_crypto_new_pk_env_evp_pkey(struct evp_pkey_st *pkey); struct evp_pkey_st *_crypto_pk_env_get_evp_pkey(crypto_pk_env_t *env, int private); struct dh_st *_crypto_dh_env_get_dh(crypto_dh_env_t *dh); diff --git a/src/common/tortls.c b/src/common/tortls.c index 1075de977e..d6a0d8488e 100644 --- a/src/common/tortls.c +++ b/src/common/tortls.c @@ -815,27 +815,22 @@ log_cert_lifetime(X509 *cert, const char *problem) tor_free(s2); } -/** If the provided tls connection is authenticated and has a - * certificate that is currently valid and signed, then set - * *identity_key to the identity certificate's key and return - * 0. Else, return -1 and log complaints with log-level severity. - */ -int -tor_tls_verify_v1(int severity, tor_tls_t *tls, crypto_pk_env_t **identity_key) +/** DOCDOC helper. + * cert_out needs to be freed. id_cert_out doesn't. */ +static void +try_to_extract_certs_from_tls(int severity, tor_tls_t *tls, + X509 **cert_out, X509 **id_cert_out) { X509 *cert = NULL, *id_cert = NULL; STACK_OF(X509) *chain = NULL; - EVP_PKEY *id_pkey = NULL; - RSA *rsa; - int num_in_chain; - int r = -1, i; - - *identity_key = NULL; + int num_in_chain, i; + *cert_out = *id_cert_out = NULL; if (!(cert = SSL_get_peer_certificate(tls->ssl))) - goto done; + return; + *cert_out = cert; if (!(chain = SSL_get_peer_cert_chain(tls->ssl))) - goto done; + return; num_in_chain = sk_X509_num(chain); /* 1 means we're receiving (server-side), and it's just the id_cert. * 2 means we're connecting (client-side), and it's both the link @@ -845,18 +840,38 @@ tor_tls_verify_v1(int severity, tor_tls_t *tls, crypto_pk_env_t **identity_key) log_fn(severity,LD_PROTOCOL, "Unexpected number of certificates in chain (%d)", num_in_chain); - goto done; + return; } for (i=0; iidentity_key to the identity certificate's key and return + * 0. Else, return -1 and log complaints with log-level severity. + */ +int +tor_tls_verify_v1(int severity, tor_tls_t *tls, crypto_pk_env_t **identity_key) +{ + X509 *cert = NULL, *id_cert = NULL; + EVP_PKEY *id_pkey = NULL; + RSA *rsa; + int r = -1; + + *identity_key = NULL; + + try_to_extract_certs_from_tls(severity, tls, &cert, &id_cert); + if (!cert) + goto done; if (!id_cert) { log_fn(severity,LD_PROTOCOL,"No distinct identity certificate found"); goto done; } - if (!(id_pkey = X509_get_pubkey(id_cert)) || X509_verify(cert, id_pkey) <= 0) { log_fn(severity,LD_PROTOCOL,"X509_verify on cert and pkey returned <= 0"); @@ -884,6 +899,111 @@ tor_tls_verify_v1(int severity, tor_tls_t *tls, crypto_pk_env_t **identity_key) return r; } +/** DOCDOC + * + * Returns 1 on "verification is done", 0 on "still need LINK_AUTH." + */ +int +tor_tls_verify_certs_v2(int severity, tor_tls_t *tls, + const char *cert_str, size_t cert_len, + const char *id_cert_str, size_t id_cert_len, + crypto_pk_env_t **cert_key_out, + char *conn_cert_digest_out, + char *id_digest_out) +{ + X509 *cert = NULL, *id_cert = NULL; + EVP_PKEY *id_pkey = NULL, *cert_pkey = NULL; + int free_id_cert = 0, peer_used_tls_cert = 0; + int r = -1; + + tor_assert(cert_key_out); + tor_assert(conn_cert_digest_out); + tor_assert(id_digest_out); + + *cert_key_out = NULL; + + if (cert_str && cert_len) { + /*XXXX020 warn on error. */ + const unsigned char *cp = (const unsigned char*) cert_str; + cert = d2i_X509(NULL, &cp, cert_len); + } + if (id_cert_str && id_cert_len) { + /*XXXX020 warn on error. */ + const unsigned char *cp = (const unsigned char*) id_cert_str; + id_cert = d2i_X509(NULL, &cp, id_cert_len); + if (id_cert) + free_id_cert = 1; + } + + if (cert) { + int cmp = 0; + X509 *cert_tmp = SSL_get_peer_certificate(tls->ssl); + if (cert_tmp) { + peer_used_tls_cert = 1; + cmp = X509_cmp(cert, cert_tmp); + X509_free(cert_tmp); + } + if (cmp != 0) { + log_fn(severity, LD_PROTOCOL, + "Certificate in CERT cell didn't match TLS cert."); + goto done; + } + } + + if (!cert || !id_cert) { + X509 *c=NULL, *id=NULL; + try_to_extract_certs_from_tls(severity, tls, &c, &id); + if (c) { + if (!cert) + cert = c; + else + X509_free(c); + } + if (id && !id_cert) + id_cert = id; + } + if (!id_cert || !cert) + goto done; + + if (!(id_pkey = X509_get_pubkey(id_cert)) || + X509_verify(cert, id_pkey) <= 0) { + log_fn(severity,LD_PROTOCOL,"X509_verify on cert and pkey returned <= 0"); + tls_log_errors(severity,"verifying certificate"); + goto done; + } + + { + crypto_pk_env_t *i = _crypto_new_pk_env_evp_pkey(id_pkey); + if (!i) + goto done; + crypto_pk_get_digest(i, id_digest_out); + crypto_free_pk_env(i); + } + if (!(cert_pkey = X509_get_pubkey(cert))) + goto done; + if (!(*cert_key_out = _crypto_new_pk_env_evp_pkey(cert_pkey))) + goto done; + + { + unsigned int len = 0; + X509_digest(cert, EVP_sha1(), (unsigned char*)conn_cert_digest_out, &len); + tor_assert(len == DIGEST_LEN); + } + + r = peer_used_tls_cert ? 1 : 0; + done: + if (cert) + X509_free(cert); + if (id_cert && free_id_cert) + X509_free(id_cert); + if (id_pkey) + EVP_PKEY_free(id_pkey); + if (cert_pkey) + EVP_PKEY_free(cert_pkey); + + return r; +} + /** Check whether the certificate set on the connection tls is * expired or not-yet-valid, give or take tolerance * seconds. Return 0 for valid, -1 for failure. diff --git a/src/common/tortls.h b/src/common/tortls.h index a0fad8488a..3a58cc2795 100644 --- a/src/common/tortls.h +++ b/src/common/tortls.h @@ -60,6 +60,12 @@ char *tor_tls_encode_my_certificate(tor_tls_t *tls, size_t *size_out, crypto_pk_env_t *tor_tls_dup_private_key(tor_tls_t *tls); int tor_tls_verify_v1(int severity, tor_tls_t *tls, crypto_pk_env_t **identity); +int tor_tls_verify_certs_v2(int severity, tor_tls_t *tls, + const char *cert_str, size_t cert_len, + const char *id_cert_str, size_t id_cert_len, + crypto_pk_env_t **cert_key_out, + char *conn_cert_digest_out, + char *id_digest_out); int tor_tls_check_lifetime(tor_tls_t *tls, int tolerance); int tor_tls_read(tor_tls_t *tls, char *cp, size_t len); int tor_tls_write(tor_tls_t *tls, const char *cp, size_t n); diff --git a/src/or/command.c b/src/or/command.c index 834d47e871..8e8aa4abdc 100644 --- a/src/or/command.c +++ b/src/or/command.c @@ -608,9 +608,10 @@ static void command_process_cert_cell(var_cell_t *cell, or_connection_t *conn) { int n_certs = 0; - uint16_t conn_cert_len, id_cert_len; + uint16_t conn_cert_len = 0, id_cert_len = 0; const char *conn_cert = NULL, *id_cert = NULL; const char *cp, *end; + int authenticated = 0; /*XXXX020 log messages*/ if (conn->_base.state != OR_CONN_STATE_OR_HANDSHAKING) @@ -633,9 +634,11 @@ command_process_cert_cell(var_cell_t *cell, or_connection_t *conn) if (end-cp < len) goto err; if (n_certs == 0) { - conn_cert = cp; - conn_cert_len = len; + id_cert = cp; + id_cert_len = len; } else if (n_certs == 1) { + conn_cert = id_cert; + conn_cert_len = id_cert_len; id_cert = cp; id_cert_len = len; } else { @@ -646,20 +649,34 @@ command_process_cert_cell(var_cell_t *cell, or_connection_t *conn) } /* Now we have 0, 1, or 2 certs. */ - - - /* Verify that identity cert has signed peer cert in SSL, or - * peer cert in the cell. */ - /* Verify that identity cert is self-signed. */ - /* Learn ID digest. */ - /* Learn cert digests. */ - /* Remember peer cert public key. */ - /* set received_certs. */ + if (n_certs == 0) { + /* The other side is unauthenticated. */ + } else { + int r; + r = tor_tls_verify_certs_v2(LOG_PROTOCOL_WARN, conn->tls, + conn_cert, conn_cert_len, + id_cert, id_cert_len, + &conn->handshake_state->signing_key, + (conn->handshake_state->started_here ? + conn->handshake_state->server_cert_digest : + conn->handshake_state->client_cert_digest), + conn->handshake_state->cert_id_digest); + if (r < 0) + goto err; + if (r == 1) + authenticated = 1; + } conn->handshake_state->received_certs = 1; + if (authenticated) { + /* XXXX020 make the connection open. */ + } + if (! conn->handshake_state->signing_key) + goto err; + return; err: - ; + /*XXXX020 close the connection */; } #define LINK_AUTH_STRING "Tor initiator certificate verification" @@ -732,7 +749,7 @@ command_process_link_auth_cell(cell_t *cell, or_connection_t *conn) /* Okay, we're authenticated. */ s->authenticated = 1; - /* XXXX020 act on being authenticated: */ + /* XXXX020 act on being authenticated: Open the connection. */ return; err: diff --git a/src/or/connection_or.c b/src/or/connection_or.c index e64844e52c..aff224774c 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -633,8 +633,9 @@ connection_or_nonopen_was_started_here(or_connection_t *conn) * this guy; and note that this guy is reachable. */ static int -connection_or_check_valid_handshake(or_connection_t *conn, int started_here, - char *digest_rcvd) +connection_or_check_valid_tls_handshake(or_connection_t *conn, + int started_here, + char *digest_rcvd_out) { crypto_pk_env_t *identity_rcvd=NULL; or_options_t *options = get_options(); @@ -677,7 +678,7 @@ connection_or_check_valid_handshake(or_connection_t *conn, int started_here, if (identity_rcvd) { has_identity=1; - crypto_pk_get_digest(identity_rcvd, digest_rcvd); + crypto_pk_get_digest(identity_rcvd, digest_rcvd_out); if (crypto_pk_cmp_keys(get_identity_key(), identity_rcvd)<0) { conn->circ_id_type = CIRC_ID_TYPE_LOWER; @@ -686,12 +687,12 @@ connection_or_check_valid_handshake(or_connection_t *conn, int started_here, } crypto_free_pk_env(identity_rcvd); } else { - memset(digest_rcvd, 0, DIGEST_LEN); + memset(digest_rcvd_out, 0, DIGEST_LEN); conn->circ_id_type = CIRC_ID_TYPE_NEITHER; } if (started_here && tor_digest_is_zero(conn->identity_digest)) { - memcpy(conn->identity_digest, digest_rcvd, DIGEST_LEN); + memcpy(conn->identity_digest, digest_rcvd_out, DIGEST_LEN); tor_free(conn->nickname); conn->nickname = tor_malloc(HEX_DIGEST_LEN+2); conn->nickname[0] = '$'; @@ -706,11 +707,11 @@ connection_or_check_valid_handshake(or_connection_t *conn, int started_here, int as_advertised = 1; tor_assert(has_cert); tor_assert(has_identity); - if (memcmp(digest_rcvd, conn->identity_digest, DIGEST_LEN)) { + if (memcmp(digest_rcvd_out, conn->identity_digest, DIGEST_LEN)) { /* I was aiming for a particular digest. I didn't get it! */ char seen[HEX_DIGEST_LEN+1]; char expected[HEX_DIGEST_LEN+1]; - base16_encode(seen, sizeof(seen), digest_rcvd, DIGEST_LEN); + base16_encode(seen, sizeof(seen), digest_rcvd_out, DIGEST_LEN); base16_encode(expected, sizeof(expected), conn->identity_digest, DIGEST_LEN); log_fn(severity, LD_OR, @@ -728,7 +729,7 @@ connection_or_check_valid_handshake(or_connection_t *conn, int started_here, * with the same address:port and a different key. */ dirserv_orconn_tls_done(conn->_base.address, conn->_base.port, - digest_rcvd, as_advertised); + digest_rcvd_out, as_advertised); } if (!as_advertised) return -1; @@ -755,7 +756,8 @@ connection_tls_finish_handshake(or_connection_t *conn) log_debug(LD_OR,"tls handshake done. verifying."); /* V1 only XXXX020 */ - if (connection_or_check_valid_handshake(conn, started_here, digest_rcvd) < 0) + if (connection_or_check_valid_tls_handshake(conn, started_here, + digest_rcvd) < 0) return -1; if (!started_here) { /* V1 only XXXX020 */