Commit 5205c7fd authored by Nick Mathewson's avatar Nick Mathewson 🥄
Browse files

Initial NSS support for TLS.

This is enough to get a chutney network to bootstrap, though a bunch
of work remains.
parent c567b8fc
......@@ -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 <time.h>
/** 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 *<b>link_cert_out</b> and *<b>id_cert_out</b> to the link certificate
* and ID certificate that we're currently using for our V3 in-protocol
* handshake's certificate chain. If <b>server</b> 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
* *<b>identity_key</b> to the identity certificate's key and return
* 0. Else, return -1 and log complaints with log-level <b>severity</b>.
*/
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 <b>tls</b> is expired
* give or take <b>past_tolerance</b> seconds, or not-yet-valid give or take
* <b>future_tolerance</b> 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;
}
......@@ -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,
......
......@@ -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);
......
......@@ -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 <winsock.h>"*/
#include <winsock2.h>
......@@ -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 <prio.h>
// For access to raw sockets.
#include <private/pprio.h>
#include <ssl.h>
#include <sslt.h>
#include <sslproto.h>
#include <certt.h>
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 = "<unrecognized>";
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 *