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
2018-08-23 20:00:47 +08:00

1051 lines
26 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <netinet/in.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <stdint.h>
#include <sys/socket.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>
#include <MESA_htable.h>
#define SSL_EX_DATA_IDX_SSLMGR 0
struct ssl_mgr
{
uint8_t sslcomp;
uint8_t no_ssl2;
uint8_t no_ssl3;
uint8_t no_tls10;
uint8_t no_tls11;
uint8_t no_tls12;
CONST_SSL_METHOD *(*sslmethod)(void);
int sslversion;
char ssl_session_context[8];
MESA_htable_handle down_sess_cache;
MESA_htable_handle up_sess_cache;
char* default_ciphers;
DH * dh;
char * ecdhcurve;
char * crlurl;
uint8_t SSL_MODE_RELEASE_BUFFERS;
};
struct ssl_stream
{
SSL * ssl;
struct ssl_mgr* ref_mgr;
char* sni;
};
struct peek_client_hello_ctx
{
unsigned char sni_peek_retries; /* max 64 SNI parse retries */
struct event* ev;
struct event_base* evbase;
};
struct ssl_connect_origin_ctx
{
struct bufferevent *bev;
struct ssl_stream* ssl;
struct sockaddr addr;
socklen_t addrlen;
};
struct ssl_mgr* init_ssl_manager(const char* ini_profile, const char* section)
{
struct ssl_mgr* mgr=ALLOC(struct ssl_mgr*, 1);
memcpy(mgr->ssl_session_context,"mesa-tfe",sizeof(mgr->ssl_session_context));
return mgr;
}
void destroy_ssl_manager(struct ssl_mgr* mgr)
{
free(mgr);
}
SSL_SESSION* up_session_get(MESA_htable_handle* cache, struct sockaddr * addr, socklen_t addr_len, const char* sni)
{
}
void up_session_set(MESA_htable_handle* cache, struct sockaddr * addr, socklen_t addr_len, const char* sni, SSL_SESSION * value)
{
}
SSL_SESSION* down_session_get(MESA_htable_handle* cache, unsigned char * id, int idlen)
{
dynbuf_t *valbuf = (dynbuf_t *)val;
MESA_htable_search_cb(const MESA_htable_handle table, const uchar * key, uint size, hash_cb_fun_t * cb, void * arg, long * cb_ret);
SSL_SESSION *sess;
const unsigned char *p;
p = (const unsigned char *)valbuf->buf;
sess = d2i_SSL_SESSION(NULL, &p, valbuf->sz); /* increments p */
if (!sess)
return NULL;
if (!ssl_session_is_valid(sess)) {
SSL_SESSION_free(sess);
return NULL;
}
if (copy)
return sess;
SSL_SESSION_free(sess);
return ((void*)-1);
}
void down_session_set(MESA_htable_handle* cache, SSL_SESSION* sess)
{
unsigned int len=0;
const unsigned char* id = SSL_SESSION_get_id(sess, &len);
unsigned char *p=NULL;
size_t asn1sz=0;
asn1sz = i2d_SSL_SESSION(sess, NULL);
p=ALLOC(char, asn1sz);
i2d_SSL_SESSION(sess, &p); /* updates p */
int ret=MESA_htable_add(cache, id, len, p);
return;
}
void down_session_del(MESA_htable_handle* cache, SSL_SESSION* sess)
{
}
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_chello* ssl_peek_result_release_chello(future_result_t* result)
{
struct ssl_chello* p=(struct ssl_chello* )result, *copy=NULL;
copy=ALLOC(struct ssl_chello*,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_chello(struct ssl_chello* 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_get_ctx(promise);
struct ssl_chello *result=NULL;
const char* reason_too_many_retries="too many tries";
const char* reason_see_no_client_hello="see no client hello";
const char* reason=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.
//or we should use sni proxy instead? https://github.com/dlundquist/sniproxy/blob/master/src/tls.c
rv = ssl_tls_clienthello_parse(buf, n, 0, &chello, &result->sni);
if(rv==0)
{
promise_dettach_ctx(promise);
promise_success(promise, result);
ssl_free_chello(result);
peek_client_hello_ctx_free(ctx);
}
else
{
if(!chello)
{
TFE_LOG_ERROR("Peeking did not yield a (truncated) ClientHello message, aborting connection\n");
reason="see no client hello";
goto failed;
}
if(ctx->sni_peek_retries++ > 50)
{
TFE_LOG_ERROR("Peek failed due to too many retries\n");
reason="too many peek retries";
goto failed;
}
/* 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;
failed:
promise_dettach_ctx(promise);
promise_failed(promise,FUTURE_ERROR_EXCEPTION,reason);
peek_client_hello_ctx_free(ctx);
ssl_free_chello(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;
}
/*
* 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(struct ssl_mgr* mgr, const struct ssl_chello* chello, struct sockaddr* addr, socklen_t addrlen)
{
SSL_CTX* sslctx=NULL;
SSL* ssl=NULL;
SSL_SESSION* sess=NULL;
sslctx = SSL_CTX_new(mgr->sslmethod());
pxy_sslctx_setoptions(sslctx, mgr);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
if (mgr->sslversion) {
if (SSL_CTX_set_min_proto_version(sslctx, chello->version) == 0 ||
SSL_CTX_set_max_proto_version(sslctx, chello->version) == 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 (chello->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 = up_session_get(mgr->up_sess_cache, addr,
addrlen, chello->sni); /* new sess inst */
if (sess)
{
SSL_set_session(ssl, sess); /* increments sess refcount */
SSL_SESSION_free(sess);
}
return ssl;
}
void ssl_stream_shutdown(struct bufferevent *bev,struct ssl_stream* ssl)
{
//TODO:
}
void ssl_connect_origin_ctx_free(struct ssl_connect_origin_ctx* ctx)
{
if(ctx->ssl!=NULL)
{
ssl_stream_shutdown(ctx->bev,ctx->ssl);
free(ctx->ssl);
}
if(ctx->bev!=NULL)
{
bufferevent_free(ctx->bev);
ctx->bev=NULL;
}
free(ctx);
return;
}
struct ssl_stream* ssl_conn_origin_result_release_stream(future_result_t* result)
{
struct ssl_connect_origin_ctx* ctx=(struct ssl_connect_origin_ctx* )result;
struct ssl_stream* ret=ctx->ssl;
ctx->ssl=NULL; //giveup ownership
return ret;
}
struct bufferevent* ssl_conn_origin_result_release_bev(future_result_t* result)
{
struct ssl_connect_origin_ctx* ctx=(struct ssl_connect_origin_ctx* )result;
struct bufferevent * ret=ctx->bev;
ctx->bev=NULL; //giveup ownership
return ret;
}
/*
* 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_get_ctx(promise);
SSL_SESSION * to_cached=NULL;
struct tfe_stream_private* _stream = (struct tfe_stream_private*)arg;
if (events & BEV_EVENT_ERROR)
{
promise_failed(promise, FUTURE_ERROR_EXCEPTION, "ssl connect failed");
}
else
{
if (events & BEV_EVENT_CONNECTED)
{
bufferevent_setcb(ctx->bev, NULL, NULL, NULL, NULL);//leave a clean bev for on_success
to_cached=SSL_get0_session(ctx->ssl->ssl);
up_session_set(ctx->ssl->ref_mgr->up_sess_cache, &(ctx->addr), ctx->addrlen, ctx->ssl->sni, to_cached);
promise_dettach_ctx(promise);
promise_success(promise, ctx);
ctx->bev=NULL;
}
else
{
assert(0);
}
}
ssl_connect_origin_ctx_free(ctx);
return
}
void ssl_async_connect_origin(struct future* future, struct ssl_mgr* mgr, const struct ssl_chello* chello, evutil_socket_t fd, struct event_base *evbase)
{
struct promise* p=future_to_promise(future);
struct ssl_connect_origin_ctx* ctx=ALLOC(struct ssl_connect_origin_ctx, 1);
int ret=0;
struct sockaddr addr;
socklen_t addrlen;
ret=getpeername(fd, &(ctx->addr), &(ctx->addrlen));
assert(ret==0);
ctx->ssl=ALLOC(struct ssl_stream,1);
ctx->ssl->ref_mgr=mgr;
ctx->ssl->sni=tfe_strdup(chello->sni);
ctx->ssl->ssl=upstream_ssl_create(mgr, chello, &(ctx->addr), ctx->addrlen);
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 cert 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 cert */
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).
*/
static int
pxy_ossl_sessnew_cb(SSL * ssl, SSL_SESSION * sess)
{
struct ssl_mgr* mgr=(struct ssl_mgr*) SSL_get_ex_data(ssl, SSL_EX_DATA_IDX_SSLMGR, mgr);
#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) {
return 0;
}
#endif /* HAVE_SSLV2 */
if (sess)
{
down_session_set(mgr->down_sess_cache, 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)
{
struct ssl_mgr* mgr=(struct ssl_mgr*) SSL_get_ex_data(ssl, SSL_EX_DATA_IDX_SSLMGR, mgr);
if (sess)
{
down_session_del(mgr->down_sess_cache, sess)
}
return;
}
/*
* Called by OpenSSL when a src SSL session is requested by the client.
OPENSSL_VERSION_NUMBER >= 0x10100000L required.
*/
static SSL_SESSION *
pxy_ossl_sessget_cb(UNUSED SSL *ssl, const unsigned char *id, int idlen, int *copy)
{
struct ssl_mgr* mgr=(struct ssl_mgr*) SSL_get_ex_data(ssl, SSL_EX_DATA_IDX_SSLMGR, mgr);
SSL_SESSION * sess;
*copy = 0; /* SSL should not increment reference count of session */
sess = (SSL_SESSION *) down_session_get(mgr->down_sess_cache,id, idlen);
return sess;
}
/*
* Set SSL_CTX options that are the same for incoming and outgoing SSL_CTX.
*/
static void
pxy_sslctx_setoptions(SSL_CTX * sslctx, struct ssl_mgr* mgr)
{
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 (mgr->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 (mgr->no_ssl3)
{
SSL_CTX_set_options(sslctx, SSL_OP_NO_SSLv3);
}
#endif /* HAVE_SSLV3 */
#ifdef HAVE_TLSV10
if (mgr->no_tls10)
{
SSL_CTX_set_options(sslctx, SSL_OP_NO_TLSv1);
}
#endif /* HAVE_TLSV10 */
#ifdef HAVE_TLSV11
if (mgr->no_tls11)
{
SSL_CTX_set_options(sslctx, SSL_OP_NO_TLSv1_1);
}
#endif /* HAVE_TLSV11 */
#ifdef HAVE_TLSV12
if (mgr->no_tls12)
{
SSL_CTX_set_options(sslctx, SSL_OP_NO_TLSv1_2);
}
#endif /* HAVE_TLSV12 */
#ifdef SSL_OP_NO_COMPRESSION
if (!mgr->sslcomp)
{
SSL_CTX_set_options(sslctx, SSL_OP_NO_COMPRESSION);
}
#endif /* SSL_OP_NO_COMPRESSION */
SSL_CTX_set_cipher_list(sslctx, mgr->default_ciphers);
}
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 cert\n");
}
}
if (ctx->opts->certgen_writeall)
{
if (pxy_srccert_write_to_gendir(ctx, ctx->origcrt, 1) == -1)
{
log_err_printf("Failed to write orig cert\n");
}
}
}
/*
* Create new SSL context for the incoming connection, based on the original
* destination SSL cert.
* Returns NULL if no suitable cert 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 cert if we want to replace the server cert here.
* We generate a new cert if the current one does not match the
* supplied servername. This should only happen if the original destination
* server supplies a cert 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 cert 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 "
"cert:\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;
}
void ssl_upstream_free(struct ssl_upstream* p)
{
X509_free(p->orig_cert);
//todo close ssl with a callback.
//SSL_free(ctx->ssl);
}
/*
* Create and set up a new SSL_CTX instance for terminating SSL.
* Set up all the necessary callbacks, the cert, the cert chain and key.
*/
static SSL_CTX * down_sslctx_create(struct ssl_mgr* mgr, struct cert* crt)
{
SSL_CTX * sslctx = SSL_CTX_new(mgr->sslmethod());
if (!sslctx)
return NULL;
pxy_sslctx_setoptions(sslctx, mgr);
//TFE's OPENSSL_VERSION_NUMBER >= 0x10100000L
if (mgr->sslversion) {
if (SSL_CTX_set_min_proto_version(sslctx,mgr->sslversion) == 0 ||
SSL_CTX_set_max_proto_version(sslctx, mgr->sslversion) == 0) {
SSL_CTX_free(sslctx);
return NULL;
}
}
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 *)(mgr->ssl_session_context),
sizeof(mgr->ssl_session_context));
#endif /* USE_SSL_SESSION_ID_CONTEXT */
#ifndef OPENSSL_NO_DH
if (mgr->dh)
{
SSL_CTX_set_tmp_dh(sslctx, mgr->dh);
}
else
{
SSL_CTX_set_tmp_dh_callback(sslctx, ssl_tmp_dh_callback);
}
#endif /* !OPENSSL_NO_DH */
#ifndef OPENSSL_NO_ECDH
if (mgr->ecdhcurve)
{
EC_KEY * ecdh = ssl_ec_by_name(mgr->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->crt);
SSL_CTX_use_PrivateKey(sslctx, crt->key);
for (int i = 0; i < sk_X509_num(crt->chain); i++)
{
X509 * c = sk_X509_value(crt->chain, i);
ssl_x509_refcount_inc(c); /* next call consumes a reference */
SSL_CTX_add_extra_chain_cert(sslctx, c);
}
return sslctx;
}
/*
* Create new SSL context for the incoming connection, based on the original
* destination SSL cert.
* Returns NULL if no suitable cert could be found.
*/
struct ssl_stream * ssl_downstream_create(struct ssl_mgr* mgr, struct ssl_chello* hello, struct cert* crt)
{
struct ssl_stream* p=NULL;
if(crt==NULL)
{
goto error_out;
}
p=ALLOC(struct ssl_stream, 1);
p->ref_mgr=mgr;
//could be optimized by store the sslctx.
SSL_CTX * sslctx = down_sslctx_create(mgr,crt);
cert_mgr_free_cert(crt);
if (!sslctx)
{
goto error_out;
}
p->ssl = SSL_new(sslctx);
int ret=SSL_set_ex_data(p->ssl, SSL_EX_DATA_IDX_SSLMGR, mgr);
assert(ret==1);
SSL_CTX_free(sslctx); /* SSL_new() increments refcount */
if (!p->ssl)
{
goto error_out;
}
if(mgr->SSL_MODE_RELEASE_BUFFERS==1)
{
/* lower memory footprint for idle connections */
SSL_set_mode( p->ssl, SSL_get_mode(p->ssl) | SSL_MODE_RELEASE_BUFFERS);
}
p->sni=tfe_strdup(hello->sni);
return p;
error_out:
free(p);
return NULL;
}
void ssl_stream_free_and_close_fd(struct ssl_stream* stream, struct event_base *evbase, evutil_socket_t fd)
{
free(stream->sni);
stream->sni=NULL;
// X509_free(p->fake_cert);
}