diff --git a/src/lib/tls/tortls.c b/src/lib/tls/tortls.c index 0b14b69f44..cc9738599e 100644 --- a/src/lib/tls/tortls.c +++ b/src/lib/tls/tortls.c @@ -4,6 +4,7 @@ /* See LICENSE for licensing information */ #define TORTLS_PRIVATE +#define TOR_X509_PRIVATE #include "lib/tls/x509.h" #include "lib/tls/x509_internal.h" #include "lib/tls/tortls.h" @@ -14,6 +15,8 @@ #include "lib/crypt_ops/crypto_rsa.h" #include "lib/crypt_ops/crypto_rand.h" +#include + /** Global TLS contexts. We keep them here because nobody else needs * to touch them. * @@ -31,6 +34,26 @@ tor_tls_context_get(int is_server) return is_server ? server_tls_context : client_tls_context; } +/** Convert an errno (or a WSAerrno on windows) into a TOR_TLS_* error + * code. */ +int +tor_errno_to_tls_error(int e) +{ + switch (e) { + case SOCK_ERRNO(ECONNRESET): // most common + return TOR_TLS_ERROR_CONNRESET; + case SOCK_ERRNO(ETIMEDOUT): + return TOR_TLS_ERROR_TIMEOUT; + case SOCK_ERRNO(EHOSTUNREACH): + case SOCK_ERRNO(ENETUNREACH): + return TOR_TLS_ERROR_NO_ROUTE; + case SOCK_ERRNO(ECONNREFUSED): + return TOR_TLS_ERROR_CONNREFUSED; // least common + default: + return TOR_TLS_ERROR_MISC; + } +} + /** Set *link_cert_out and *id_cert_out to the link certificate * and ID certificate that we're currently using for our V3 in-protocol * handshake's certificate chain. If server is true, provide the certs @@ -334,3 +357,102 @@ tor_tls_is_server(tor_tls_t *tls) tor_assert(tls); return tls->isServer; } + +/** Release resources associated with a TLS object. Does not close the + * underlying file descriptor. + */ +void +tor_tls_free_(tor_tls_t *tls) +{ + if (!tls) + return; + tor_assert(tls->ssl); + { + size_t r,w; + tor_tls_get_n_raw_bytes(tls,&r,&w); /* ensure written_by_tls is updated */ + } + tor_tls_impl_free_(tls->ssl); + tls->ssl = NULL; +#ifdef ENABLE_OPENSSL + tls->negotiated_callback = NULL; +#endif + if (tls->context) + tor_tls_context_decref(tls->context); + tor_free(tls->address); + tls->magic = 0x99999999; + tor_free(tls); +} + +/** If the provided tls connection is authenticated and has a + * certificate chain 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(int severity, tor_tls_t *tls, crypto_pk_t **identity) +{ + tor_x509_cert_impl_t *cert = NULL, *id_cert = NULL; + tor_x509_cert_t *peer_x509 = NULL, *id_x509 = NULL; + tor_assert(tls); + tor_assert(identity); + int rv = -1; + + 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; + } + peer_x509 = tor_x509_cert_new(cert); + id_x509 = tor_x509_cert_new(id_cert); + cert = id_cert = NULL; /* Prevent double-free */ + + if (! tor_tls_cert_is_valid(severity, peer_x509, id_x509, time(NULL), 0)) { + goto done; + } + + *identity = tor_tls_cert_get_key(id_x509); + rv = 0; + + done: + if (cert) + tor_x509_cert_impl_free_(cert); + if (id_cert) + tor_x509_cert_impl_free_(id_cert); + tor_x509_cert_free(peer_x509); + tor_x509_cert_free(id_x509); + + return rv; +} + +/** Check whether the certificate set on the connection tls is expired + * give or take past_tolerance seconds, or not-yet-valid give or take + * future_tolerance seconds. Return 0 for valid, -1 for failure. + * + * NOTE: you should call tor_tls_verify before tor_tls_check_lifetime. + */ +int +tor_tls_check_lifetime(int severity, tor_tls_t *tls, + time_t now, + int past_tolerance, int future_tolerance) +{ + tor_x509_cert_t *cert; + int r = -1; + + if (!(cert = tor_tls_get_peer_cert(tls))) + goto done; + + if (tor_x509_check_cert_lifetime_internal(severity, cert->cert, now, + past_tolerance, + future_tolerance) < 0) + goto done; + + r = 0; + done: + tor_x509_cert_free(cert); + /* Not expected to get invoked */ + tls_log_errors(tls, LOG_WARN, LD_NET, "checking certificate lifetime"); + + return r; +} diff --git a/src/lib/tls/tortls.h b/src/lib/tls/tortls.h index 306d321cd4..7bbb42b2fd 100644 --- a/src/lib/tls/tortls.h +++ b/src/lib/tls/tortls.h @@ -13,10 +13,25 @@ #include "lib/crypt_ops/crypto_rsa.h" #include "lib/testsupport/testsupport.h" +#include "lib/net/nettypes.h" /* Opaque structure to hold a TLS connection. */ typedef struct tor_tls_t tor_tls_t; +#ifdef TORTLS_PRIVATE +#ifdef ENABLE_OPENSSL +struct ssl_st; +struct ssl_ctx_st; +struct ssl_session_st; +typedef struct ssl_ctx_st tor_tls_context_impl_t; +typedef struct ssl_st tor_tls_impl_t; +#else +struct PRFileDesc; +typedef struct PRFileDesc tor_tls_context_impl_t; +typedef struct PRFileDesc tor_tls_impl_t; +#endif +#endif + struct tor_x509_cert_t; /* Possible return values for most tor_tls_* functions. */ @@ -73,7 +88,7 @@ int tor_tls_context_init(unsigned flags, void tor_tls_context_incref(tor_tls_context_t *ctx); void tor_tls_context_decref(tor_tls_context_t *ctx); tor_tls_context_t *tor_tls_context_get(int is_server); -tor_tls_t *tor_tls_new(int sock, int is_server); +tor_tls_t *tor_tls_new(tor_socket_t sock, int is_server); void tor_tls_set_logged_address(tor_tls_t *tls, const char *address); void tor_tls_set_renegotiate_callback(tor_tls_t *tls, void (*cb)(tor_tls_t *, void *arg), @@ -121,13 +136,17 @@ MOCK_DECL(int,tor_tls_export_key_material,( size_t context_len, const char *label)); +#ifdef ENABLE_OPENSSL /* Log and abort if there are unhandled TLS errors in OpenSSL's error stack. */ #define check_no_tls_errors() check_no_tls_errors_(__FILE__,__LINE__) - void check_no_tls_errors_(const char *fname, int line); + void tor_tls_log_one_error(tor_tls_t *tls, unsigned long err, int severity, int domain, const char *doing); +#else +#define check_no_tls_errors() STMT_NIL +#endif int tor_tls_get_my_certs(int server, const struct tor_x509_cert_t **link_cert_out, diff --git a/src/lib/tls/tortls_internal.h b/src/lib/tls/tortls_internal.h index 2920e96585..b9e01e0c54 100644 --- a/src/lib/tls/tortls_internal.h +++ b/src/lib/tls/tortls_internal.h @@ -6,15 +6,11 @@ #ifndef TORTLS_INTERNAL_H #define TORTLS_INTERNAL_H -#ifdef ENABLE_OPENSSL -struct ssl_st; -struct ssl_ctx_st; -struct ssl_session_st; -#endif - int tor_errno_to_tls_error(int e); +#ifdef ENABLE_OPENSSL int tor_tls_get_error(tor_tls_t *tls, int r, int extra, const char *doing, int severity, int domain); +#endif MOCK_DECL(void, try_to_extract_certs_from_tls, (int severity, tor_tls_t *tls, tor_x509_cert_impl_t **cert_out, @@ -31,13 +27,9 @@ int tor_tls_context_init_certificates(tor_tls_context_t *result, crypto_pk_t *identity, unsigned key_lifetime, unsigned flags); +void tor_tls_impl_free_(tor_tls_impl_t *ssl); -#ifdef ENABLE_OPENSSL -void tor_tls_context_impl_free(struct ssl_ctx_st *); -#else -struct ssl_ctx_st; // XXXX replace -void tor_tls_context_impl_free(struct ssl_ctx_st *); -#endif +void tor_tls_context_impl_free(tor_tls_context_impl_t *); #ifdef ENABLE_OPENSSL tor_tls_t *tor_tls_get_by_ssl(const struct ssl_st *ssl); diff --git a/src/lib/tls/tortls_nss.c b/src/lib/tls/tortls_nss.c index 35dbc27d9c..d2b81ad084 100644 --- a/src/lib/tls/tortls_nss.c +++ b/src/lib/tls/tortls_nss.c @@ -12,6 +12,7 @@ #include "orconfig.h" #define TORTLS_PRIVATE +#define TOR_X509_PRIVATE #ifdef _WIN32 /*wrkard for dtls1.h >= 0.9.8m of "#include "*/ #include @@ -22,6 +23,9 @@ #include "lib/crypt_ops/crypto_rand.h" #include "lib/crypt_ops/crypto_dh.h" #include "lib/crypt_ops/crypto_util.h" +#include "lib/crypt_ops/crypto_nss_mgt.h" +#include "lib/string/printf.h" + #include "lib/tls/x509.h" #include "lib/tls/x509_internal.h" #include "lib/tls/tortls.h" @@ -29,26 +33,16 @@ #include "lib/tls/tortls_internal.h" #include "lib/log/util_bug.h" -int -tor_errno_to_tls_error(int e) -{ - (void)e; - // XXXX - return -1; -} -int -tor_tls_get_error(tor_tls_t *tls, int r, int extra, - const char *doing, int severity, int domain) -{ - (void)tls; - (void)r; - (void)extra; - (void)doing; - (void)severity; - (void)domain; - // XXXX - return -1; -} +#include +// For access to raw sockets. +#include +#include +#include +#include +#include + +static SECStatus always_accept_cert_cb(void *, PRFileDesc *, PRBool, PRBool); + MOCK_IMPL(void, try_to_extract_certs_from_tls,(int severity, tor_tls_t *tls, tor_x509_cert_impl_t **cert_out, @@ -57,14 +51,109 @@ try_to_extract_certs_from_tls,(int severity, tor_tls_t *tls, tor_assert(tls); tor_assert(cert_out); tor_assert(id_cert_out); - (void)severity; - // XXXX + (void) severity; + + *cert_out = *id_cert_out = NULL; + + CERTCertificate *peer = SSL_PeerCertificate(tls->ssl); + if (!peer) + return; + *cert_out = peer; /* Now owns pointer. */ + + CERTCertList *chain = SSL_PeerCertificateChain(tls->ssl); + CERTCertListNode *c = CERT_LIST_HEAD(chain); + for (; !CERT_LIST_END(c, chain); c = CERT_LIST_NEXT(c)) { + if (CERT_CompareCerts(c->cert, peer) == PR_FALSE) { + *id_cert_out = CERT_DupCertificate(c->cert); + break; + } + } + CERT_DestroyCertList(chain); +} + +static bool +we_like_ssl_cipher(SSLCipherAlgorithm ca) +{ + switch (ca) { + case ssl_calg_null: return false; + case ssl_calg_rc4: return false; + case ssl_calg_rc2: return false; + case ssl_calg_des: return false; + case ssl_calg_3des: return false; /* ???? */ + case ssl_calg_idea: return false; + case ssl_calg_fortezza: return false; + case ssl_calg_camellia: return false; + case ssl_calg_seed: return false; + + case ssl_calg_aes: return true; + case ssl_calg_aes_gcm: return true; + case ssl_calg_chacha20: return true; + default: return true; + } +} +static bool +we_like_ssl_kea(SSLKEAType kt) +{ + switch (kt) { + case ssl_kea_null: return false; + case ssl_kea_rsa: return false; /* ??? */ + case ssl_kea_fortezza: return false; + case ssl_kea_ecdh_psk: return false; + case ssl_kea_dh_psk: return false; + + case ssl_kea_dh: return true; + case ssl_kea_ecdh: return true; + case ssl_kea_tls13_any: return true; + + case ssl_kea_size: return true; /* prevent a warning. */ + default: return true; + } +} + +static bool +we_like_mac_algorithm(SSLMACAlgorithm ma) +{ + switch (ma) { + case ssl_mac_null: return false; + case ssl_mac_md5: return false; + case ssl_hmac_md5: return false; + + case ssl_mac_sha: return true; + case ssl_hmac_sha: return true; + case ssl_hmac_sha256: return true; + case ssl_mac_aead: return true; + case ssl_hmac_sha384: return true; + default: return true; + } +} + +static bool +we_like_auth_type(SSLAuthType at) +{ + switch (at) { + case ssl_auth_null: return false; + case ssl_auth_rsa_decrypt: return false; + case ssl_auth_dsa: return false; + case ssl_auth_kea: return false; + + case ssl_auth_ecdsa: return true; + case ssl_auth_ecdh_rsa: return true; + case ssl_auth_ecdh_ecdsa: return true; + case ssl_auth_rsa_sign: return true; + case ssl_auth_rsa_pss: return true; + case ssl_auth_psk: return true; + case ssl_auth_tls13_any: return true; + + case ssl_auth_size: return true; /* prevent a warning. */ + default: return true; + } } tor_tls_context_t * tor_tls_context_new(crypto_pk_t *identity, unsigned int key_lifetime, unsigned flags, int is_client) { + SECStatus s; tor_assert(identity); tor_tls_context_t *ctx = tor_malloc_zero(sizeof(tor_tls_context_t)); @@ -77,7 +166,128 @@ tor_tls_context_new(crypto_pk_t *identity, } } - // XXXX write the main body. + { + /* Create the "model" PRFileDesc that we will use to base others on. */ + PRFileDesc *tcp = PR_NewTCPSocket(); + if (!tcp) + goto err; + + ctx->ctx = SSL_ImportFD(NULL, tcp); + if (!ctx->ctx) { + PR_Close(tcp); + goto err; + } + } + + // Configure the certificate. + if (!is_client) { + s = SSL_ConfigServerCert(ctx->ctx, + ctx->my_link_cert->cert, + (SECKEYPrivateKey *) + crypto_pk_get_nss_privkey(ctx->link_key), + NULL, /* ExtraServerCertData */ + 0 /* DataLen */); + if (s != SECSuccess) + goto err; + } + + // We need a certificate from the other side. + if (is_client) { + // XXXX does this do anything? + s = SSL_OptionSet(ctx->ctx, SSL_REQUIRE_CERTIFICATE, PR_TRUE); + if (s != SECSuccess) + goto err; + } + + // Always accept other side's cert; we'll check it ourselves in goofy + // tor ways. + s = SSL_AuthCertificateHook(ctx->ctx, always_accept_cert_cb, NULL); + + // We allow simultaneous read and write. + s = SSL_OptionSet(ctx->ctx, SSL_ENABLE_FDX, PR_TRUE); + if (s != SECSuccess) + goto err; + // XXXX SSL_ROLLBACK_DETECTION?? + // XXXX SSL_ENABLE_ALPN?? + + // Force client-mode or server_mode. + s = SSL_OptionSet(ctx->ctx, + is_client ? SSL_HANDSHAKE_AS_CLIENT : SSL_HANDSHAKE_AS_SERVER, + PR_TRUE); + if (s != SECSuccess) + goto err; + + // Disable everything before TLS 1.0; support everything else. + { + SSLVersionRange vrange; + memset(&vrange, 0, sizeof(vrange)); + s = SSL_VersionRangeGetSupported(ssl_variant_stream, &vrange); + if (s != SECSuccess) + goto err; + if (vrange.min < SSL_LIBRARY_VERSION_TLS_1_0) + vrange.min = SSL_LIBRARY_VERSION_TLS_1_0; + s = SSL_VersionRangeSet(ctx->ctx, &vrange); + if (s != SECSuccess) + goto err; + } + + // Only support strong ciphers. + { + const PRUint16 *ciphers = SSL_GetImplementedCiphers(); + const PRUint16 n_ciphers = SSL_GetNumImplementedCiphers(); + PRUint16 i; + for (i = 0; i < n_ciphers; ++i) { + SSLCipherSuiteInfo info; + memset(&info, 0, sizeof(info)); + s = SSL_GetCipherSuiteInfo(ciphers[i], &info, sizeof(info)); + if (s != SECSuccess) + goto err; + if (BUG(info.cipherSuite != ciphers[i])) + goto err; + int disable = info.effectiveKeyBits < 128 || + info.macBits < 128 || + !we_like_ssl_cipher(info.symCipher) || + !we_like_ssl_kea(info.keaType) || + !we_like_mac_algorithm(info.macAlgorithm) || + !we_like_auth_type(info.authType)/* Requires NSS 3.24 */; + + s = SSL_CipherPrefSet(ctx->ctx, ciphers[i], + disable ? PR_FALSE : PR_TRUE); + if (s != SECSuccess) + goto err; + } + } + + // Only use DH and ECDH keys once. + s = SSL_OptionSet(ctx->ctx, SSL_REUSE_SERVER_ECDHE_KEY, PR_FALSE); + if (s != SECSuccess) + goto err; + + // don't cache sessions. + s = SSL_OptionSet(ctx->ctx, SSL_NO_CACHE, PR_TRUE); + if (s != SECSuccess) + goto err; + + // Enable DH. + s = SSL_OptionSet(ctx->ctx, SSL_ENABLE_SERVER_DHE, PR_TRUE); + if (s != SECSuccess) + goto err; + + // Set DH and ECDH groups. + SSLNamedGroup groups[] = { + ssl_grp_ec_curve25519, + ssl_grp_ec_secp256r1, + ssl_grp_ec_secp224r1, + ssl_grp_ffdhe_2048, + }; + s = SSL_NamedGroupConfig(ctx->ctx, groups, ARRAY_LENGTH(groups)); + if (s != SECSuccess) + goto err; + + // These features are off by default, so we don't need to disable them: + // Session tickets + // Renegotiation + // Compression goto done; err: @@ -88,11 +298,9 @@ tor_tls_context_new(crypto_pk_t *identity, } void -tor_tls_context_impl_free(struct ssl_ctx_st *ctx) +tor_tls_context_impl_free(tor_tls_context_impl_t *ctx) { - (void)ctx; - // XXXX - // XXXX openssl type. + PR_Close(ctx); } void @@ -101,33 +309,82 @@ tor_tls_get_state_description(tor_tls_t *tls, char *buf, size_t sz) (void)tls; (void)buf; (void)sz; - // XXXX + // AFAICT, NSS doesn't expose its internal state. + buf[0]=0; } void tor_tls_init(void) { - // XXXX + /* We don't have any global setup to do yet, but that will change */ } + void tls_log_errors(tor_tls_t *tls, int severity, int domain, const char *doing) { + /* XXXX This implementation isn't right for NSS -- it logs the last error + whether anything actually failed or not. */ + (void)tls; - (void)severity; - (void)domain; - (void)doing; - // XXXX + PRErrorCode code = PORT_GetError(); + + const char *string = PORT_ErrorToString(code); + const char *name = PORT_ErrorToName(code); + char buf[16]; + if (!string) + string = ""; + if (!name) { + tor_snprintf(buf, sizeof(buf), "%d", code); + name = buf; + } + + if (doing) { + log_fn(severity, domain, "TLS error %s while %s: %s", name, doing, string); + } else { + log_fn(severity, domain, "TLS error %s: %s", name, string); + } } tor_tls_t * -tor_tls_new(int sock, int is_server) +tor_tls_new(tor_socket_t sock, int is_server) { (void)sock; - (void)is_server; - // XXXX - return NULL; + tor_tls_context_t *ctx = tor_tls_context_get(is_server); + + PRFileDesc *tcp = PR_ImportTCPSocket(sock); + if (!tcp) + return NULL; + + PRFileDesc *ssl = SSL_ImportFD(ctx->ctx, tcp); + if (!ssl) { + PR_Close(tcp); + return NULL; + } + + tor_tls_t *tls = tor_malloc_zero(sizeof(tor_tls_t)); + tls->magic = TOR_TLS_MAGIC; + tls->context = ctx; + tor_tls_context_incref(ctx); + tls->ssl = ssl; + tls->socket = sock; + tls->state = TOR_TLS_ST_HANDSHAKE; + tls->isServer = !!is_server; + + if (!is_server) { + /* Set a random SNI */ + char *fake_hostname = crypto_random_hostname(4,25, "www.",".com"); + SSL_SetURL(tls->ssl, fake_hostname); + tor_free(fake_hostname); + } + SECStatus s = SSL_ResetHandshake(ssl, is_server ? PR_TRUE : PR_FALSE); + if (s != SECSuccess) { + crypto_nss_log_errors(LOG_WARN, "resetting handshake state"); + } + + return tls; } + void tor_tls_set_renegotiate_callback(tor_tls_t *tls, void (*cb)(tor_tls_t *, void *arg), @@ -136,131 +393,175 @@ tor_tls_set_renegotiate_callback(tor_tls_t *tls, tor_assert(tls); (void)cb; (void)arg; - // XXXX; + + /* We don't support renegotiation-based TLS with NSS. */ } void -tor_tls_free_(tor_tls_t *tls) +tor_tls_impl_free_(tor_tls_impl_t *tls) { - (void)tls; - // XXXX + // XXXX This will close the underlying fd, which our OpenSSL version does + // not do! + + PR_Close(tls); } int tor_tls_peer_has_cert(tor_tls_t *tls) { - (void)tls; - // XXXX - return -1; + CERTCertificate *cert = SSL_PeerCertificate(tls->ssl); + int result = (cert != NULL); + CERT_DestroyCertificate(cert); + return result; } + MOCK_IMPL(tor_x509_cert_t *, tor_tls_get_peer_cert,(tor_tls_t *tls)) { - tor_assert(tls); - // XXXX - return NULL; + CERTCertificate *cert = SSL_PeerCertificate(tls->ssl); + if (cert) + return tor_x509_cert_new(cert); + else + return NULL; } + MOCK_IMPL(tor_x509_cert_t *, tor_tls_get_own_cert,(tor_tls_t *tls)) { tor_assert(tls); - // XXXX - return NULL; -} -int -tor_tls_verify(int severity, tor_tls_t *tls, crypto_pk_t **identity) -{ - tor_assert(tls); - tor_assert(identity); - (void)severity; - // XXXX - return -1; -} -int -tor_tls_check_lifetime(int severity, - tor_tls_t *tls, time_t now, - int past_tolerance, - int future_tolerance) -{ - tor_assert(tls); - (void)severity; - (void)now; - (void)past_tolerance; - (void)future_tolerance; - // XXXX - return -1; + CERTCertificate *cert = SSL_LocalCertificate(tls->ssl); + if (cert) + return tor_x509_cert_new(cert); + else + return NULL; } + MOCK_IMPL(int, tor_tls_read, (tor_tls_t *tls, char *cp, size_t len)) { tor_assert(tls); tor_assert(cp); - (void)len; - // XXXX - return -1; + tor_assert(len < INT_MAX); + + PRInt32 rv = PR_Read(tls->ssl, cp, (int)len); + // log_debug(LD_NET, "PR_Read(%zu) returned %d", n, (int)rv); + if (rv > 0) { + tls->n_read_since_last_check += rv; + return rv; + } + if (rv == 0) + return TOR_TLS_CLOSE; + PRErrorCode err = PORT_GetError(); + if (err == PR_WOULD_BLOCK_ERROR) { + return TOR_TLS_WANTREAD; // XXXX ???? + } else { + crypto_nss_log_errors(LOG_NOTICE, "reading"); // XXXX + return TOR_TLS_ERROR_MISC; // ???? + } } + int tor_tls_write(tor_tls_t *tls, const char *cp, size_t n) { tor_assert(tls); - tor_assert(cp); - (void)n; - // XXXX - return -1; + tor_assert(cp || n == 0); + tor_assert(n < INT_MAX); + + PRInt32 rv = PR_Write(tls->ssl, cp, (int)n); + // log_debug(LD_NET, "PR_Write(%zu) returned %d", n, (int)rv); + if (rv > 0) { + tls->n_written_since_last_check += rv; + return rv; + } + if (rv == 0) + return TOR_TLS_ERROR_MISC; + PRErrorCode err = PORT_GetError(); + + if (err == PR_WOULD_BLOCK_ERROR) { + return TOR_TLS_WANTWRITE; // XXXX ???? + } else { + crypto_nss_log_errors(LOG_NOTICE, "writing"); // XXXX + return TOR_TLS_ERROR_MISC; // ???? + } } + int tor_tls_handshake(tor_tls_t *tls) { tor_assert(tls); - // XXXX - return -1; + tor_assert(tls->state == TOR_TLS_ST_HANDSHAKE); + + SECStatus s = SSL_ForceHandshake(tls->ssl); + if (s == SECSuccess) { + tls->state = TOR_TLS_ST_OPEN; + log_debug(LD_NET, "SSL handshake is supposedly complete."); + return tor_tls_finish_handshake(tls); + } + if (PORT_GetError() == PR_WOULD_BLOCK_ERROR) + return TOR_TLS_WANTREAD; /* XXXX What about wantwrite? */ + + return TOR_TLS_ERROR_MISC; // XXXX } + int tor_tls_finish_handshake(tor_tls_t *tls) { tor_assert(tls); - // XXXX - return -1; + // We don't need to do any of the weird handshake nonsense stuff on NSS, + // since we only support recent handshakes. + return TOR_TLS_DONE; } + void tor_tls_unblock_renegotiation(tor_tls_t *tls) { tor_assert(tls); - // XXXX + /* We don't support renegotiation with NSS. */ } + void tor_tls_block_renegotiation(tor_tls_t *tls) { tor_assert(tls); - // XXXX + /* We don't support renegotiation with NSS. */ } + void tor_tls_assert_renegotiation_unblocked(tor_tls_t *tls) { tor_assert(tls); - // XXXX + /* We don't support renegotiation with NSS. */ } + int tor_tls_shutdown(tor_tls_t *tls) { tor_assert(tls); - // XXXX + /* XXXX This is not actually used, so I'm not implementing it. We can + * XXXX remove this function entirely someday. */ return -1; } + int tor_tls_get_pending_bytes(tor_tls_t *tls) { tor_assert(tls); - // XXXX - return -1; + int n = SSL_DataPending(tls->ssl); + if (n < 0) { + crypto_nss_log_errors(LOG_WARN, "Looking up pending bytes"); + return 0; + } + return (int)n; } + size_t tor_tls_get_forced_write_size(tor_tls_t *tls) { tor_assert(tls); - // XXXX + /* NSS doesn't have the same "forced write" restriction as openssl. */ return 0; } + void tor_tls_get_n_raw_bytes(tor_tls_t *tls, size_t *n_read, size_t *n_written) @@ -268,7 +569,13 @@ tor_tls_get_n_raw_bytes(tor_tls_t *tls, tor_assert(tls); tor_assert(n_read); tor_assert(n_written); - // XXXX + /* XXXX We don't curently have a way to measure this information correctly + * in NSS; we could do that with a PRIO layer, but it'll take a little + * coding. For now, we just track the number of bytes sent _in_ the TLS + * stream. Doing this will make our rate-limiting slightly inaccurate. */ + *n_read = tls->n_read_since_last_check; + *n_written = tls->n_written_since_last_check; + tls->n_read_since_last_check = tls->n_written_since_last_check = 0; } int @@ -281,54 +588,70 @@ tor_tls_get_buffer_sizes(tor_tls_t *tls, tor_assert(rbuf_bytes); tor_assert(wbuf_capacity); tor_assert(wbuf_bytes); - // XXXX + + /* This is an acceptable way to say "we can't measure this." */ return -1; } + MOCK_IMPL(double, tls_get_write_overhead_ratio, (void)) { - // XXXX - return 0.0; + /* XXX We don't currently have a way to measure this in NSS; we could do that + * XXX with a PRIO layer, but it'll take a little coding. */ + return 0.95; } int tor_tls_used_v1_handshake(tor_tls_t *tls) { tor_assert(tls); - // XXXX - return -1; -} -int -tor_tls_get_num_server_handshakes(tor_tls_t *tls) -{ - tor_assert(tls); - // XXXX - return -1; + /* We don't support or allow the V1 handshake with NSS. + */ + return 0; } + int tor_tls_server_got_renegotiate(tor_tls_t *tls) { tor_assert(tls); - // XXXX - return -1; + return 0; /* We don't support renegotiation with NSS */ } + MOCK_IMPL(int, tor_tls_cert_matches_key,(const tor_tls_t *tls, const struct tor_x509_cert_t *cert)) { tor_assert(tls); tor_assert(cert); - // XXXX - return 0; + int rv = 0; + + CERTCertificate *peercert = SSL_PeerCertificate(tls->ssl); + if (!peercert) + goto done; + CERTSubjectPublicKeyInfo *peer_info = &peercert->subjectPublicKeyInfo; + CERTSubjectPublicKeyInfo *cert_info = &cert->cert->subjectPublicKeyInfo; + rv = SECOID_CompareAlgorithmID(&peer_info->algorithm, + &cert_info->algorithm) == 0 && + SECITEM_ItemsAreEqual(&peer_info->subjectPublicKey, + &cert_info->subjectPublicKey); + + done: + if (peercert) + CERT_DestroyCertificate(peercert); + return rv; } + MOCK_IMPL(int, tor_tls_get_tlssecrets,(tor_tls_t *tls, uint8_t *secrets_out)) { tor_assert(tls); tor_assert(secrets_out); - // XXXX + + /* There's no way to get this information out of NSS. */ + return -1; } + MOCK_IMPL(int, tor_tls_export_key_material,(tor_tls_t *tls, uint8_t *secrets_out, const uint8_t *context, @@ -339,42 +662,72 @@ tor_tls_export_key_material,(tor_tls_t *tls, uint8_t *secrets_out, tor_assert(secrets_out); tor_assert(context); tor_assert(label); - (void)context_len; - // XXXX - return -1; -} + tor_assert(strlen(label) <= UINT_MAX); + tor_assert(context_len <= UINT_MAX); -void -check_no_tls_errors_(const char *fname, int line) -{ - (void)fname; - (void)line; - // XXXX -} -void -tor_tls_log_one_error(tor_tls_t *tls, unsigned long err, - int severity, int domain, const char *doing) -{ - tor_assert(tls); - (void)err; - (void)severity; - (void)domain; - (void)doing; - // XXXX + SECStatus s; + s = SSL_ExportKeyingMaterial(tls->ssl, + label, (unsigned)strlen(label), + PR_TRUE, context, (unsigned)context_len, + secrets_out, DIGEST256_LEN); + + return (s == SECSuccess) ? 0 : -1; } const char * tor_tls_get_ciphersuite_name(tor_tls_t *tls) { tor_assert(tls); - // XXXX - return NULL; + + SSLChannelInfo channel_info; + SSLCipherSuiteInfo cipher_info; + + memset(&channel_info, 0, sizeof(channel_info)); + memset(&cipher_info, 0, sizeof(cipher_info)); + + SECStatus s = SSL_GetChannelInfo(tls->ssl, + &channel_info, sizeof(channel_info)); + if (s != SECSuccess) + return NULL; + + s = SSL_GetCipherSuiteInfo(channel_info.cipherSuite, + &cipher_info, sizeof(cipher_info)); + if (s != SECSuccess) + return NULL; + + return cipher_info.cipherSuiteName; } +/** The group we should use for ecdhe when none was selected. */ +#define SEC_OID_TOR_DEFAULT_ECDHE_GROUP SEC_OID_ANSIX962_EC_PRIME256V1 + int evaluate_ecgroup_for_tls(const char *ecgroup) { - (void)ecgroup; - // XXXX - return -1; + SECOidTag tag; + + if (!ecgroup) + tag = SEC_OID_TOR_DEFAULT_ECDHE_GROUP; + else if (!strcasecmp(ecgroup, "P256")) + tag = SEC_OID_ANSIX962_EC_PRIME256V1; + else if (!strcasecmp(ecgroup, "P224")) + tag = SEC_OID_SECG_EC_SECP224R1; + else + return 0; + + /* I don't think we need any additional tests here for NSS */ + (void) tag; + + return 1; +} + +static SECStatus +always_accept_cert_cb(void *arg, PRFileDesc *ssl, PRBool checkSig, + PRBool isServer) +{ + (void)arg; + (void)ssl; + (void)checkSig; + (void)isServer; + return SECSuccess; } diff --git a/src/lib/tls/tortls_openssl.c b/src/lib/tls/tortls_openssl.c index fb6328bcd0..c4e9e7770f 100644 --- a/src/lib/tls/tortls_openssl.c +++ b/src/lib/tls/tortls_openssl.c @@ -244,26 +244,6 @@ tls_log_errors(tor_tls_t *tls, int severity, int domain, const char *doing) } } -/** Convert an errno (or a WSAerrno on windows) into a TOR_TLS_* error - * code. */ -int -tor_errno_to_tls_error(int e) -{ - switch (e) { - case SOCK_ERRNO(ECONNRESET): // most common - return TOR_TLS_ERROR_CONNRESET; - case SOCK_ERRNO(ETIMEDOUT): - return TOR_TLS_ERROR_TIMEOUT; - case SOCK_ERRNO(EHOSTUNREACH): - case SOCK_ERRNO(ENETUNREACH): - return TOR_TLS_ERROR_NO_ROUTE; - case SOCK_ERRNO(ECONNREFUSED): - return TOR_TLS_ERROR_CONNREFUSED; // least common - default: - return TOR_TLS_ERROR_MISC; - } -} - #define CATCH_SYSCALL 1 #define CATCH_ZERO 2 @@ -952,8 +932,6 @@ tor_tls_server_info_callback(const SSL *ssl, int type, int val) /* Check whether we're watching for renegotiates. If so, this is one! */ if (tls->negotiated_callback) tls->got_renegotiate = 1; - if (tls->server_handshake_count < 127) /*avoid any overflow possibility*/ - ++tls->server_handshake_count; } else { log_warn(LD_BUG, "Couldn't look up the tls for an SSL*. How odd!"); return; @@ -1167,30 +1145,13 @@ tor_tls_assert_renegotiation_unblocked(tor_tls_t *tls) #endif /* defined(SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION) && ... */ } -/** Release resources associated with a TLS object. Does not close the - * underlying file descriptor. - */ void -tor_tls_free_(tor_tls_t *tls) +tor_tls_impl_free_(tor_tls_impl_t *ssl) { - if (!tls) - return; - tor_assert(tls->ssl); - { - size_t r,w; - tor_tls_get_n_raw_bytes(tls,&r,&w); /* ensure written_by_tls is updated */ - } #ifdef SSL_set_tlsext_host_name - SSL_set_tlsext_host_name(tls->ssl, NULL); + SSL_set_tlsext_host_name(ssl, NULL); #endif - SSL_free(tls->ssl); - tls->ssl = NULL; - tls->negotiated_callback = NULL; - if (tls->context) - tor_tls_context_decref(tls->context); - tor_free(tls->address); - tls->magic = 0x99999999; - tor_free(tls); + SSL_free(ssl); } /** Underlying function for TLS reading. Reads up to len @@ -1509,90 +1470,6 @@ try_to_extract_certs_from_tls,(int severity, tor_tls_t *tls, *id_cert_out = id_cert; } -/** If the provided tls connection is authenticated and has a - * certificate chain 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(int severity, tor_tls_t *tls, crypto_pk_t **identity_key) -{ - X509 *cert = NULL, *id_cert = NULL; - EVP_PKEY *id_pkey = NULL; - RSA *rsa; - int r = -1; - - check_no_tls_errors(); - *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; - } - tls_log_errors(tls, severity, LD_HANDSHAKE, "before verifying certificate"); - - 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(tls, severity, LD_HANDSHAKE, "verifying certificate"); - goto done; - } - - rsa = EVP_PKEY_get1_RSA(id_pkey); - if (!rsa) - goto done; - *identity_key = crypto_new_pk_from_openssl_rsa_(rsa); - - r = 0; - - done: - if (cert) - X509_free(cert); - if (id_pkey) - EVP_PKEY_free(id_pkey); - - /* This should never get invoked, but let's make sure in case OpenSSL - * acts unexpectedly. */ - tls_log_errors(tls, LOG_WARN, LD_HANDSHAKE, "finishing tor_tls_verify"); - - return r; -} - -/** Check whether the certificate set on the connection tls is expired - * give or take past_tolerance seconds, or not-yet-valid give or take - * future_tolerance seconds. Return 0 for valid, -1 for failure. - * - * NOTE: you should call tor_tls_verify before tor_tls_check_lifetime. - */ -int -tor_tls_check_lifetime(int severity, tor_tls_t *tls, - time_t now, - int past_tolerance, int future_tolerance) -{ - X509 *cert; - int r = -1; - - if (!(cert = SSL_get_peer_certificate(tls->ssl))) - goto done; - - if (tor_x509_check_cert_lifetime_internal(severity, cert, now, - past_tolerance, - future_tolerance) < 0) - goto done; - - r = 0; - done: - if (cert) - X509_free(cert); - /* Not expected to get invoked */ - tls_log_errors(tls, LOG_WARN, LD_NET, "checking certificate lifetime"); - - return r; -} - /** Return the number of bytes available for reading from tls. */ int @@ -1690,14 +1567,6 @@ tor_tls_used_v1_handshake(tor_tls_t *tls) return ! tls->wasV2Handshake; } -/** Return the number of server handshakes that we've noticed doing on - * tls. */ -int -tor_tls_get_num_server_handshakes(tor_tls_t *tls) -{ - return tls->server_handshake_count; -} - /** Return true iff the server TLS connection tls got the renegotiation * request it was waiting for. */ int diff --git a/src/lib/tls/tortls_st.h b/src/lib/tls/tortls_st.h index 897be497ef..a1b59a37af 100644 --- a/src/lib/tls/tortls_st.h +++ b/src/lib/tls/tortls_st.h @@ -6,6 +6,8 @@ #ifndef TOR_TORTLS_ST_H #define TOR_TORTLS_ST_H +#include "lib/net/socket.h" + #define TOR_TLS_MAGIC 0x71571571 typedef enum { @@ -17,7 +19,7 @@ typedef enum { struct tor_tls_context_t { int refcnt; - struct ssl_ctx_st *ctx; + tor_tls_context_impl_t *ctx; struct tor_x509_cert_t *my_link_cert; struct tor_x509_cert_t *my_id_cert; struct tor_x509_cert_t *my_auth_cert; @@ -31,8 +33,9 @@ struct tor_tls_context_t { struct tor_tls_t { uint32_t magic; tor_tls_context_t *context; /** A link to the context object for this tls. */ - struct ssl_st *ssl; /**< An OpenSSL SSL object. */ - int socket; /**< The underlying file descriptor for this TLS connection. */ + tor_tls_impl_t *ssl; /**< An OpenSSL SSL object or NSS PRFileDesc. */ + tor_socket_t socket; /**< The underlying file descriptor for this TLS + * connection. */ char *address; /**< An address to log when describing this connection. */ tor_tls_state_bitfield_t state : 3; /**< The current SSL state, * depending on which operations @@ -45,11 +48,10 @@ struct tor_tls_t { * one certificate). */ /** True iff we should call negotiated_callback when we're done reading. */ unsigned int got_renegotiate:1; +#ifdef ENABLE_OPENSSL /** Return value from tor_tls_classify_client_ciphers, or 0 if we haven't * called that function yet. */ int8_t client_cipher_list_type; - /** Incremented every time we start the server side of a handshake. */ - uint8_t server_handshake_count; size_t wantwrite_n; /**< 0 normally, >0 if we returned wantwrite last * time. */ /** Last values retrieved from BIO_number_read()/write(); see @@ -62,6 +64,11 @@ struct tor_tls_t { void (*negotiated_callback)(tor_tls_t *tls, void *arg); /** Argument to pass to negotiated_callback. */ void *callback_arg; +#endif +#ifdef ENABLE_NSS + size_t n_read_since_last_check; + size_t n_written_since_last_check; +#endif }; #endif diff --git a/src/test/test_tortls.c b/src/test/test_tortls.c index eedf0dd3c9..0e4b5afafa 100644 --- a/src/test/test_tortls.c +++ b/src/test/test_tortls.c @@ -72,6 +72,7 @@ test_tortls_err_to_string(void *data) (void)1; } +#ifdef ENABLE_OPENSSL static int mock_tls_cert_matches_key(const tor_tls_t *tls, const tor_x509_cert_t *cert) { @@ -105,6 +106,7 @@ test_tortls_tor_tls_get_error(void *data) crypto_pk_free(key2); tor_tls_free(tls); } +#endif static void test_tortls_x509_cert_get_id_digests(void *ignored) @@ -165,6 +167,7 @@ test_tortls_get_my_certs(void *ignored) (void)1; } +#ifdef ENABLE_OPENSSL static void test_tortls_get_forced_write_size(void *ignored) { @@ -203,23 +206,6 @@ test_tortls_used_v1_handshake(void *ignored) tor_free(tls); } -static void -test_tortls_get_num_server_handshakes(void *ignored) -{ - (void)ignored; - int ret; - tor_tls_t *tls; - - tls = tor_malloc_zero(sizeof(tor_tls_t)); - - tls->server_handshake_count = 3; - ret = tor_tls_get_num_server_handshakes(tls); - tt_int_op(ret, OP_EQ, 3); - - done: - tor_free(tls); -} - static void test_tortls_server_got_renegotiate(void *ignored) { @@ -236,6 +222,7 @@ test_tortls_server_got_renegotiate(void *ignored) done: tor_free(tls); } +#endif static void test_tortls_evaluate_ecgroup_for_tls(void *ignored) @@ -266,13 +253,14 @@ test_tortls_evaluate_ecgroup_for_tls(void *ignored) struct testcase_t tortls_tests[] = { LOCAL_TEST_CASE(errno_to_tls_error, 0), LOCAL_TEST_CASE(err_to_string, 0), - LOCAL_TEST_CASE(tor_tls_get_error, 0), LOCAL_TEST_CASE(x509_cert_get_id_digests, 0), LOCAL_TEST_CASE(get_my_certs, TT_FORK), +#ifdef ENABLE_OPENSSL + LOCAL_TEST_CASE(tor_tls_get_error, 0), LOCAL_TEST_CASE(get_forced_write_size, 0), LOCAL_TEST_CASE(used_v1_handshake, TT_FORK), - LOCAL_TEST_CASE(get_num_server_handshakes, 0), LOCAL_TEST_CASE(server_got_renegotiate, 0), +#endif LOCAL_TEST_CASE(evaluate_ecgroup_for_tls, 0), END_OF_TESTCASES };