This repository has been archived on 2025-09-14. You can view files and clone it, but cannot push or open issues or pull requests.
Files
tango-tfe/platform/src/ssl_stream.cpp

952 lines
24 KiB
C++
Raw Normal View History

#include <netinet/in.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <event2/bufferevent_ssl.h>
#include <event2/buffer.h>
#include <event2/thread.h>
#include <event2/dns.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <tfe_stream.h>
#include <tfe_utils.h>
#include <tfe_future.h>
#include <stream.h>
#include <cert.h>
#include <ssl.h>
struct peek_client_hello_ctx
{
unsigned char sni_peek_retries; /* max 64 SNI parse retries */
struct event* ev;
struct event_base* evbase;
};
void peek_client_hello_ctx_free(void* ctx)
{
struct peek_client_hello_ctx * _ctx=(struct peek_client_hello_ctx *)ctx;
event_free(_ctx->ev);
_ctx->ev = NULL;
free(_ctx->sni);
_ctx->sni=NULL;
free(_ctx);
return;
}
struct ssl_client_hello* ssl_get_peek_result(future_result_t* result)
{
struct ssl_client_hello* p=(struct ssl_client_hello* )result, *copy=NULL;
copy=ALLOC(struct ssl_client_hello*,1);
if(p!=NULL)
{
copy->sni=tfe_strdup(p->sni);
copy->cipher_suites=tfe_strdup(p->cipher_suites);
copy->version=p->version;
}
return copy;
}
void ssl_free_peek_result(struct ssl_client_hello* p)
{
if(p==NULL)
{
return;
}
free(p->sni);
p->sni=NULL;
free(p->cipher_suites);
p->cipher_suites=NULL;
p->cipher_suites=NULL;
free(p);
return;
}
static void peek_client_hello_cb(evutil_socket_t fd, short what, void * arg)
{
struct promise* promise=(struct promise*)arg;
struct peek_client_hello_ctx* ctx= (struct peek_client_hello_ctx*)promise->ctx;
struct ssl_client_hello *result=NULL;
memset(&result, 0, sizeof(result));
char* sni=NULL;
unsigned char buf[1024];
ssize_t n=0;
const unsigned char * chello=NULL;
int rv=0;
n = recv(fd, buf, sizeof(buf), MSG_PEEK);
if (n == -1)
{
TFE_LOG_ERROR("Error peeking on fd, aborting connection\n");
goto failed;
}
if (n == 0)
{
goto failed;
}
result=ssl_get_peek_result(NULL);
//todo: parse version and cipher suites.
rv = ssl_tls_clienthello_parse(buf, n, 0, &chello, &result->sni);
if ((rv == 1) && !chello)
{
TFE_LOG_ERROR("Peeking did not yield a (truncated) ClientHello message, aborting connection\n");
goto failed;
}
if ((rv == 1) && chello && (ctx->sni_peek_retries++ < 50))
{
/* ssl_tls_clienthello_parse indicates that we
* should retry later when we have more data, and we
* haven't reached the maximum retry count yet.
* Reschedule this event as timeout-only event in
* order to prevent busy looping over the read event.
* Because we only peeked at the pending bytes and
* never actually read them, fd is still ready for
* reading now. We use 25 * 0.2 s = 5 s timeout. */
struct timeval retry_delay = {0, 100};
event_free(ctx->ev);
ctx->ev = event_new(ctx->evbase, fd, 0, peek_client_hello_cb, promise);
assert(ctx->ev!=NULL);
event_add(ctx->ev, &retry_delay);
return;
}
promise_dettach_ctx(promise);
promise_success(promise, result);
ssl_free_peek_result(result);
peek_client_hello_ctx_free(ctx);
return;
failed:
promise_failed(promise,FUTURE_ERROR_EXCEPTION,"too many tries");
peek_client_hello_ctx_free(ctx);
promise_dettach_ctx(promise);
ssl_free_peek_result(result);
return;
}
void ssl_async_peek_client_hello(struct future* future, evutil_socket_t fd, struct event_base *evbase)
{
struct event * ev=NULL;
struct promise* p=future_to_promise(future);
struct peek_client_hello_ctx* ctx=ALLOC(struct peek_client_hello_ctx, 1);
ctx->ev = event_new(evbase, fd, EV_READ, peek_client_hello_cb, p);
event_add(evbase, NULL);
promise_set_ctx(p, ctx, peek_client_hello_ctx_free);
return;
}
struct ssl_connect_origin_ctx
{
struct bufferevent *bev;
SSL* ssl;
};
void ssl_connect_origin_ctx_free(struct ssl_connect_origin_ctx* ctx)
{
ctx->ssl=NULL;
bufferevent_free(ctx->bev);
ctx->bev=NULL;
//Do not free bev and ssl, reserved for user.
free(ctx);
}
/*
* Callback for meta events on the up- and downstream connection bufferevents.
* Called when EOF has been reached, a connection has been made, and on errors.
*/
static void ssl_connect_origin_eventcb(struct bufferevent * bev, short events, void * arg)
{
struct promise* promise=(struct promise*)arg;
struct ssl_connect_origin_ctx* ctx=(struct ssl_connect_origin_ctx*)promise_dettach_ctx(promise);
struct tfe_stream_private* _stream = (struct tfe_stream_private*)arg;
if (events & BEV_EVENT_ERROR)
{
promise->f.cb_failed(FUTURE_ERROR_EXCEPTION,"ssl connect failed",promise->f.user);
}
else
{
if (events & BEV_EVENT_CONNECTED)
{
promise->f.cb_success(ctx->bev,promise->f.user);
ctx->bev=NULL;
}
else
{
assert(0);
}
}
ssl_connect_origin_ctx_free(ctx);
return
}
void ssl_async_connect_origin(struct future* future, const struct ssl_client_hello* client_hello, evutil_socket_t fd, const char* sni, struct event_base *evbase)
{
struct promise* p=future_to_promise(future);
struct ssl_connect_origin_ctx* ctx=ALLOC(struct ssl_connect_origin_ctx, 1);
ctx->ssl=upstream_ssl_create(opts,sni);
ctx->bev = bufferevent_openssl_socket_new(evbase, fd, ctx->ssl, BUFFEREVENT_SSL_CONNECTING, BEV_OPT_DEFER_CALLBACKS);
promise_set_ctx(p, ctx, ssl_connect_origin_ctx_free);
bufferevent_openssl_set_allow_dirty_shutdown(ctx->bev, 1);
bufferevent_setcb(ctx->bev, NULL, NULL, ssl_connect_origin_eventcb, p);
bufferevent_enable(ctx->bev, EV_READ | EV_WRITE);
}
/*
* Dump information on a certificate to the debug log.
*/
static void pxy_debug_crt(X509 * crt)
{
char * sj = ssl_x509_subject(crt);
if (sj)
{
log_dbg_printf("Subject DN: %s\n", sj);
free(sj);
}
char * names = ssl_x509_names_to_str(crt);
if (names)
{
log_dbg_printf("Common Names: %s\n", names);
free(names);
}
char * fpr;
if (!(fpr = ssl_x509_fingerprint(crt, 1)))
{
log_err_printf("Warning: Error generating X509 fingerprint\n");
}
else
{
log_dbg_printf("Fingerprint: %s\n", fpr);
free(fpr);
}
#ifdef DEBUG_CERTIFICATE
/* dump certificate */
log_dbg_print_free(ssl_x509_to_str(crt));
log_dbg_print_free(ssl_x509_to_pem(crt));
#endif /* DEBUG_CERTIFICATE */
}
static void
pxy_log_connect_nonhttp(pxy_conn_ctx_t * ctx)
{
char * msg;
int rv;
/*
* The following ifdef's within asprintf arguments list generates
* warnings with -Wembedded-directive on some compilers.
* Not fixing the code in order to avoid more code duplication.
*/
if (!ctx->src.ssl)
{
rv = asprintf(&msg, "%s %s %s %s %s"
#ifdef HAVE_LOCAL_PROCINFO
" %s"
#endif /* HAVE_LOCAL_PROCINFO */
"\n",
ctx->passthrough ? "passthrough" : "tcp",
STRORDASH(ctx->srchost_str),
STRORDASH(ctx->srcport_str),
STRORDASH(ctx->dsthost_str),
STRORDASH(ctx->dstport_str)
);
}
else
{
rv = asprintf(&msg, "%s %s %s %s %s "
"sni:%s names:%s "
"sproto:%s:%s dproto:%s:%s "
"origcrt:%s usedcrt:%s\n",
ctx->clienthello_found ? "upgrade" : "ssl",
STRORDASH(ctx->srchost_str),
STRORDASH(ctx->srcport_str),
STRORDASH(ctx->dsthost_str),
STRORDASH(ctx->dstport_str),
STRORDASH(ctx->sni),
STRORDASH(ctx->ssl_names),
SSL_get_version(ctx->src.ssl),
SSL_get_cipher(ctx->src.ssl),
SSL_get_version(ctx->dst.ssl),
SSL_get_cipher(ctx->dst.ssl),
STRORDASH(ctx->origcrtfpr),
STRORDASH(ctx->usedcrtfpr)
);
}
if ((rv < 0) || !msg)
{
ctx->enomem = 1;
goto out;
}
if (!ctx->opts->detach)
{
log_err_printf("%s", msg);
}
if (ctx->opts->connectlog)
{
}
else
{
free(msg);
}
out:
return;
}
static void
pxy_log_connect_http(pxy_conn_ctx_t * ctx)
{
char * msg;
int rv;
#ifdef DEBUG_PROXY
if (ctx->passthrough) {
log_err_printf("Warning: pxy_log_connect_http called while in "
"passthrough mode\n");
return;
}
#endif
/*
* The following ifdef's within asprintf arguments list generates
* warnings with -Wembedded-directive on some compilers.
* Not fixing the code in order to avoid more code duplication.
*/
if (!ctx->spec->ssl)
{
rv = asprintf(&msg, "http %s %s %s %s %s %s %s %s %s %s\n",
STRORDASH(ctx->srchost_str),
STRORDASH(ctx->srcport_str),
STRORDASH(ctx->dsthost_str),
STRORDASH(ctx->dstport_str),
STRORDASH(ctx->http_host),
STRORDASH(ctx->http_method),
STRORDASH(ctx->http_uri),
STRORDASH(ctx->http_status_code),
STRORDASH(ctx->http_content_length),
ctx->ocsp_denied ? " ocsp:denied" : "");
}
else
{
rv = asprintf(&msg, "https %s %s %s %s %s %s %s %s %s "
"sni:%s names:%s "
"sproto:%s:%s dproto:%s:%s "
"origcrt:%s usedcrt:%s"
#ifdef HAVE_LOCAL_PROCINFO
" %s"
#endif /* HAVE_LOCAL_PROCINFO */
"%s\n",
STRORDASH(ctx->srchost_str),
STRORDASH(ctx->srcport_str),
STRORDASH(ctx->dsthost_str),
STRORDASH(ctx->dstport_str),
STRORDASH(ctx->http_host),
STRORDASH(ctx->http_method),
STRORDASH(ctx->http_uri),
STRORDASH(ctx->http_status_code),
STRORDASH(ctx->http_content_length),
STRORDASH(ctx->sni),
STRORDASH(ctx->ssl_names),
SSL_get_version(ctx->src.ssl),
SSL_get_cipher(ctx->src.ssl),
SSL_get_version(ctx->dst.ssl),
SSL_get_cipher(ctx->dst.ssl),
STRORDASH(ctx->origcrtfpr),
STRORDASH(ctx->usedcrtfpr),
ctx->ocsp_denied ? " ocsp:denied" : "");
}
if ((rv < 0) || !msg)
{
ctx->enomem = 1;
goto out;
}
if (!ctx->opts->detach)
{
log_err_printf("%s", msg);
}
if (ctx->opts->connectlog)
{
}
else
{
free(msg);
}
out:
return;
}
/*
* Called by OpenSSL when a new src SSL session is created.
* OpenSSL increments the refcount before calling the callback and will
* decrement it again if we return 0. Returning 1 will make OpenSSL skip
* the refcount decrementing. In other words, return 0 if we did not
* keep a pointer to the object (which we never do here).
*/
#ifdef HAVE_SSLV2
#define MAYBE_UNUSED
#else /* !HAVE_SSLV2 */
#define MAYBE_UNUSED UNUSED
#endif /* !HAVE_SSLV2 */
static int
pxy_ossl_sessnew_cb(MAYBE_UNUSED SSL * ssl, SSL_SESSION * sess)
#undef MAYBE_UNUSED
{
#ifdef DEBUG_SESSION_CACHE
log_dbg_printf("===> OpenSSL new session callback:\n");
if (sess) {
log_dbg_print_free(ssl_session_to_str(sess));
} else {
log_dbg_printf("(null)\n");
}
#endif /* DEBUG_SESSION_CACHE */
#ifdef HAVE_SSLV2
/* Session resumption seems to fail for SSLv2 with protocol
* parsing errors, so we disable caching for SSLv2. */
if (SSL_version(ssl) == SSL2_VERSION) {
log_err_printf("Warning: Session resumption denied to SSLv2"
"client.\n");
return 0;
}
#endif /* HAVE_SSLV2 */
if (sess)
{
cachemgr_ssess_set(sess);
}
return 0;
}
/*
* Called by OpenSSL when a src SSL session should be removed.
* OpenSSL calls SSL_SESSION_free() after calling the callback;
* we do not need to free the reference here.
*/
static void
pxy_ossl_sessremove_cb(UNUSED SSL_CTX * sslctx, SSL_SESSION * sess)
{
#ifdef DEBUG_SESSION_CACHE
log_dbg_printf("===> OpenSSL remove session callback:\n");
if (sess) {
log_dbg_print_free(ssl_session_to_str(sess));
} else {
log_dbg_printf("(null)\n");
}
#endif /* DEBUG_SESSION_CACHE */
if (sess)
{
cachemgr_ssess_del(sess);
}
}
/*
* Called by OpenSSL when a src SSL session is requested by the client.
*/
static SSL_SESSION *
#if OPENSSL_VERSION_NUMBER < 0x10100000L
pxy_ossl_sessget_cb(UNUSED SSL * ssl, unsigned char * id, int idlen, int * copy)
#else /* OPENSSL_VERSION_NUMBER >= 0x10100000L */
pxy_ossl_sessget_cb(UNUSED SSL *ssl, const unsigned char *id, int idlen, int *copy)
#endif /* OPENSSL_VERSION_NUMBER >= 0x10100000L */
{
SSL_SESSION * sess;
#ifdef DEBUG_SESSION_CACHE
log_dbg_printf("===> OpenSSL get session callback:\n");
#endif /* DEBUG_SESSION_CACHE */
*copy = 0; /* SSL should not increment reference count of session */
sess = (SSL_SESSION *) cachemgr_ssess_get(id, idlen);
#ifdef DEBUG_SESSION_CACHE
if (sess) {
log_dbg_print_free(ssl_session_to_str(sess));
}
#endif /* DEBUG_SESSION_CACHE */
log_dbg_printf("SSL session cache: %s\n", sess ? "HIT" : "MISS");
return sess;
}
/*
* Set SSL_CTX options that are the same for incoming and outgoing SSL_CTX.
*/
static void
pxy_sslctx_setoptions(SSL_CTX * sslctx, tfe_config opts)
{
SSL_CTX_set_options(sslctx, SSL_OP_ALL);
#ifdef SSL_OP_TLS_ROLLBACK_BUG
SSL_CTX_set_options(sslctx, SSL_OP_TLS_ROLLBACK_BUG);
#endif /* SSL_OP_TLS_ROLLBACK_BUG */
#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
SSL_CTX_set_options(sslctx, SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION);
#endif /* SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION */
#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
SSL_CTX_set_options(sslctx, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS);
#endif /* SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS */
#ifdef SSL_OP_NO_TICKET
SSL_CTX_set_options(sslctx, SSL_OP_NO_TICKET);
#endif /* SSL_OP_NO_TICKET */
#ifdef SSL_OP_NO_SSLv2
#ifdef HAVE_SSLV2
if (opts->no_ssl2) {
#endif /* HAVE_SSLV2 */
SSL_CTX_set_options(sslctx, SSL_OP_NO_SSLv2);
#ifdef HAVE_SSLV2
}
#endif /* HAVE_SSLV2 */
#endif /* !SSL_OP_NO_SSLv2 */
#ifdef HAVE_SSLV3
if (opts->no_ssl3)
{
SSL_CTX_set_options(sslctx, SSL_OP_NO_SSLv3);
}
#endif /* HAVE_SSLV3 */
#ifdef HAVE_TLSV10
if (opts->no_tls10)
{
SSL_CTX_set_options(sslctx, SSL_OP_NO_TLSv1);
}
#endif /* HAVE_TLSV10 */
#ifdef HAVE_TLSV11
if (opts->no_tls11)
{
SSL_CTX_set_options(sslctx, SSL_OP_NO_TLSv1_1);
}
#endif /* HAVE_TLSV11 */
#ifdef HAVE_TLSV12
if (opts->no_tls12)
{
SSL_CTX_set_options(sslctx, SSL_OP_NO_TLSv1_2);
}
#endif /* HAVE_TLSV12 */
#ifdef SSL_OP_NO_COMPRESSION
if (!opts->sslcomp)
{
SSL_CTX_set_options(sslctx, SSL_OP_NO_COMPRESSION);
}
#endif /* SSL_OP_NO_COMPRESSION */
SSL_CTX_set_cipher_list(sslctx, opts->ciphers);
}
/*
* Create and set up a new SSL_CTX instance for terminating SSL.
* Set up all the necessary callbacks, the certificate, the cert chain and key.
*/
static SSL_CTX * downstream_sslctx_create(
pxy_conn_ctx_t * ctx, X509 * crt, STACK_OF(X509) * chain,
EVP_PKEY * key, struct tfe_config* opts, void* cb_arg)
{
SSL_CTX * sslctx = SSL_CTX_new(opts->sslmethod());
if (!sslctx)
return NULL;
pxy_sslctx_setoptions(sslctx, opts);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
if (opts->sslversion) {
if (SSL_CTX_set_min_proto_version(sslctx, opts->sslversion) == 0 ||
SSL_CTX_set_max_proto_version(sslctx, opts->sslversion) == 0) {
SSL_CTX_free(sslctx);
return NULL;
}
}
#endif /* OPENSSL_VERSION_NUMBER >= 0x10100000L */
SSL_CTX_sess_set_new_cb(sslctx, pxy_ossl_sessnew_cb);
SSL_CTX_sess_set_remove_cb(sslctx, pxy_ossl_sessremove_cb);
SSL_CTX_sess_set_get_cb(sslctx, pxy_ossl_sessget_cb);
SSL_CTX_set_session_cache_mode(sslctx, SSL_SESS_CACHE_SERVER |
SSL_SESS_CACHE_NO_INTERNAL);
#ifdef USE_SSL_SESSION_ID_CONTEXT
SSL_CTX_set_session_id_context(sslctx, (void *)(&ssl_session_context),
sizeof(ssl_session_context));
#endif /* USE_SSL_SESSION_ID_CONTEXT */
#ifndef OPENSSL_NO_TLSEXT
SSL_CTX_set_tlsext_servername_callback(sslctx, pxy_ossl_servername_cb);
SSL_CTX_set_tlsext_servername_arg(sslctx, ctx);
#endif /* !OPENSSL_NO_TLSEXT */
#ifndef OPENSSL_NO_DH
if (ctx->opts->dh)
{
SSL_CTX_set_tmp_dh(sslctx, ctx->opts->dh);
}
else
{
SSL_CTX_set_tmp_dh_callback(sslctx, ssl_tmp_dh_callback);
}
#endif /* !OPENSSL_NO_DH */
#ifndef OPENSSL_NO_ECDH
if (ctx->opts->ecdhcurve)
{
EC_KEY * ecdh = ssl_ec_by_name(ctx->opts->ecdhcurve);
SSL_CTX_set_tmp_ecdh(sslctx, ecdh);
EC_KEY_free(ecdh);
}
else
{
EC_KEY * ecdh = ssl_ec_by_name(NULL);
SSL_CTX_set_tmp_ecdh(sslctx, ecdh);
EC_KEY_free(ecdh);
}
#endif /* !OPENSSL_NO_ECDH */
SSL_CTX_use_certificate(sslctx, crt);
SSL_CTX_use_PrivateKey(sslctx, key);
for (int i = 0; i < sk_X509_num(chain); i++)
{
X509 * c = sk_X509_value(chain, i);
ssl_x509_refcount_inc(c); /* next call consumes a reference */
SSL_CTX_add_extra_chain_cert(sslctx, c);
}
#ifdef DEBUG_SESSION_CACHE
if (OPTS_DEBUG(ctx->opts)) {
int mode = SSL_CTX_get_session_cache_mode(sslctx);
log_dbg_printf("SSL session cache mode: %08x\n", mode);
if (mode == SSL_SESS_CACHE_OFF)
log_dbg_printf("SSL_SESS_CACHE_OFF\n");
if (mode & SSL_SESS_CACHE_CLIENT)
log_dbg_printf("SSL_SESS_CACHE_CLIENT\n");
if (mode & SSL_SESS_CACHE_SERVER)
log_dbg_printf("SSL_SESS_CACHE_SERVER\n");
if (mode & SSL_SESS_CACHE_NO_AUTO_CLEAR)
log_dbg_printf("SSL_SESS_CACHE_NO_AUTO_CLEAR\n");
if (mode & SSL_SESS_CACHE_NO_INTERNAL_LOOKUP)
log_dbg_printf("SSL_SESS_CACHE_NO_INTERNAL_LOOKUP\n");
if (mode & SSL_SESS_CACHE_NO_INTERNAL_STORE)
log_dbg_printf("SSL_SESS_CACHE_NO_INTERNAL_STORE\n");
}
#endif /* DEBUG_SESSION_CACHE */
return sslctx;
}
static int
pxy_srccert_write_to_gendir(pxy_conn_ctx_t * ctx, X509 * crt, int is_orig)
{
char * fn;
int rv;
if (!ctx->origcrtfpr)
return -1;
if (is_orig)
{
rv = asprintf(&fn, "%s/%s.crt", ctx->opts->certgendir,
ctx->origcrtfpr);
}
else
{
if (!ctx->usedcrtfpr)
return -1;
rv = asprintf(&fn, "%s/%s-%s.crt", ctx->opts->certgendir,
ctx->origcrtfpr, ctx->usedcrtfpr);
}
if (rv == -1)
{
ctx->enomem = 1;
return -1;
}
free(fn);
return rv;
}
static void pxy_srccert_write(pxy_conn_ctx_t * ctx)
{
if (ctx->opts->certgen_writeall || ctx->generated_cert)
{
if (pxy_srccert_write_to_gendir(ctx,
SSL_get_certificate(ctx->src.ssl), 0) == -1)
{
log_err_printf("Failed to write used certificate\n");
}
}
if (ctx->opts->certgen_writeall)
{
if (pxy_srccert_write_to_gendir(ctx, ctx->origcrt, 1) == -1)
{
log_err_printf("Failed to write orig certificate\n");
}
}
}
/*
* Create new SSL context for the incoming connection, based on the original
* destination SSL certificate.
* Returns NULL if no suitable certificate could be found.
*/
static SSL * downstream_ssl_create(cert_t * cert, struct tfe_config* opts)
{
assert(cert!=NULL);
SSL_CTX * sslctx = downstream_sslctx_create(ctx, cert->crt, cert->chain,
cert->key);
if (!sslctx)
{
return NULL;
}
SSL * ssl = SSL_new(sslctx);
SSL_CTX_free(sslctx); /* SSL_new() increments refcount */
if (!ssl)
{
return NULL;
}
#ifdef SSL_MODE_RELEASE_BUFFERS
/* lower memory footprint for idle connections */
SSL_set_mode(ssl, SSL_get_mode(ssl) | SSL_MODE_RELEASE_BUFFERS);
#endif /* SSL_MODE_RELEASE_BUFFERS */
return ssl;
}
/*
* OpenSSL servername callback, called when OpenSSL receives a servername
* TLS extension in the clientHello. Must switch to a new SSL_CTX with
* a different certificate if we want to replace the server cert here.
* We generate a new certificate if the current one does not match the
* supplied servername. This should only happen if the original destination
* server supplies a certificate which does not match the server name we
* indicate to it.
*/
static int pxy_ossl_servername_cb(SSL * ssl, UNUSED int * al, void * arg)
{
pxy_conn_ctx_t * ctx = (pxy_conn_ctx_t *) arg;
const char * sn;
X509 * sslcrt;
if (!(sn = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name)))
return SSL_TLSEXT_ERR_NOACK;
if (!ctx->sni)
{
if (OPTS_DEBUG(ctx->opts))
{
log_dbg_printf("Warning: SNI parser yielded no "
"hostname, copying OpenSSL one: "
"[NULL] != [%s]\n", sn);
}
ctx->sni = strdup(sn);
if (!ctx->sni)
{
ctx->enomem = 1;
return SSL_TLSEXT_ERR_NOACK;
}
}
if (OPTS_DEBUG(ctx->opts))
{
if (strcmp(sn, ctx->sni) != 0)
{
/*
* This may happen if the client resumes a session, but
* uses a different SNI hostname when resuming than it
* used when the session was created. OpenSSL
* correctly ignores the SNI in the ClientHello in this
* case, but since we have already sent the SNI onwards
* to the original destination, there is no way back.
* We log an error and hope this never happens.
*/
log_dbg_printf("Warning: SNI parser yielded different "
"hostname than OpenSSL callback for "
"the same ClientHello message: "
"[%s] != [%s]\n", ctx->sni, sn);
}
}
/* generate a new certificate with sn as additional altSubjectName
* and replace it both in the current SSL ctx and in the cert cache */
if (!ctx->immutable_cert &&
!ssl_x509_names_match((sslcrt = SSL_get_certificate(ssl)), sn))
{
X509 * newcrt;
SSL_CTX * newsslctx;
if (OPTS_DEBUG(ctx->opts))
{
log_dbg_printf("Certificate cache: UPDATE "
"(SNI mismatch)\n");
}
newcrt = ssl_x509_forge(ctx->opts->cacrt, ctx->opts->cakey,
sslcrt, ctx->opts->key,
sn, ctx->opts->crlurl);
if (!newcrt)
{
ctx->enomem = 1;
return SSL_TLSEXT_ERR_NOACK;
}
cachemgr_fkcrt_set(ctx->origcrt, newcrt);
ctx->generated_cert = 1;
if (OPTS_DEBUG(ctx->opts))
{
log_dbg_printf("===> Updated forged server "
"certificate:\n");
pxy_debug_crt(newcrt);
}
if (WANT_CONNECT_LOG(ctx))
{
if (ctx->ssl_names)
{
free(ctx->ssl_names);
}
ctx->ssl_names = ssl_x509_names_to_str(newcrt);
if (!ctx->ssl_names)
{
ctx->enomem = 1;
}
}
if (WANT_CONNECT_LOG(ctx) || ctx->opts->certgendir)
{
if (ctx->usedcrtfpr)
{
free(ctx->usedcrtfpr);
}
ctx->usedcrtfpr = ssl_x509_fingerprint(newcrt, 0);
if (!ctx->usedcrtfpr)
{
ctx->enomem = 1;
}
}
newsslctx = downstream_sslctx_create(ctx, newcrt, ctx->opts->chain,
ctx->opts->key);
if (!newsslctx)
{
X509_free(newcrt);
ctx->enomem = 1;
return SSL_TLSEXT_ERR_NOACK;
}
SSL_set_SSL_CTX(ssl, newsslctx); /* decr's old incr new refc */
SSL_CTX_free(newsslctx);
X509_free(newcrt);
}
else if (OPTS_DEBUG(ctx->opts))
{
log_dbg_printf("Certificate cache: KEEP (SNI match or "
"target mode)\n");
}
return SSL_TLSEXT_ERR_OK;
}
/*
* Create new SSL context for outgoing connections to the original destination.
* If hostname sni is provided, use it for Server Name Indication.
*/
static SSL* upstream_ssl_create(const struct ssl_client_hello* client_hello, int sslversion, const char* sni, MESA_htable_handle* dsess_cache)
{
SSL_CTX* sslctx=NULL;
SSL* ssl=NULL;
SSL_SESSION* sess=NULL;
sslctx = SSL_CTX_new(opts->sslmethod());
pxy_sslctx_setoptions(sslctx, opts);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
if (opts->sslversion) {
if (SSL_CTX_set_min_proto_version(sslctx, opts->sslversion) == 0 ||
SSL_CTX_set_max_proto_version(sslctx, opts->sslversion) == 0) {
SSL_CTX_free(sslctx);
return NULL;
}
}
#endif /* OPENSSL_VERSION_NUMBER >= 0x10100000L */
SSL_CTX_set_verify(sslctx, SSL_VERIFY_NONE, NULL);
ssl = SSL_new(sslctx);
SSL_CTX_free(sslctx); /* SSL_new() increments refcount */
if (!ssl)
{
return NULL;
}
#ifndef OPENSSL_NO_TLSEXT
if (sni)
{
SSL_set_tlsext_host_name(ssl, sni);
}
#endif /* !OPENSSL_NO_TLSEXT */
#ifdef SSL_MODE_RELEASE_BUFFERS
/* lower memory footprint for idle connections */
SSL_set_mode(ssl, SSL_get_mode(ssl) | SSL_MODE_RELEASE_BUFFERS);
#endif /* SSL_MODE_RELEASE_BUFFERS */
//Todo: Refactor below session resumption.
/* session resuming based on remote endpoint address and port */
sess = (SSL_SESSION *) cachemgr_dsess_get((struct sockaddr *) &ctx->addr,
ctx->addrlen, ctx->sni); /* new sess inst */
if (sess)
{
if (OPTS_DEBUG(opts))
{
log_dbg_printf("Attempt reuse dst SSL session\n");
}
SSL_set_session(ssl, sess); /* increments sess refcount */
SSL_SESSION_free(sess);
}
return ssl;
}
/*
struct ssl_upstream* ssl_upstream_create(tfe_config*opts, const char* sni)
{
struct ssl_upstream* upstream=NULL;
SSL* ssl=NULL;
ssl=upstream_ssl_create(opts,sni);
if(ssl==NULL)
{
return NULL;
}
upstream=ALLOC(struct ssl_upstream,1);
upstream->ssl=ssl;
return upstream;
}*/
void ssl_upstream_free(struct ssl_upstream* p)
{
X509_free(p->orig_cert);
//todo close ssl with a callback.
//SSL_free(ctx->ssl);
}
struct ssl_downstream* ssl_downstream_create(void)
{
struct ssl_downstream* p=NULL;
p=ALLOC(struct ssl_downstream, 1);
return p;
}
void ssl_downstream_free(struct ssl_downstream* p)
{
free(p->sni);
p->sni=NULL;
// X509_free(p->fake_cert);
}