Merge branch 'feature-ssl-stream' into 'develop-tfe3a'

Feature ssl stream

See merge request tango/tfe!5
This commit is contained in:
郑超
2018-08-24 19:25:23 +08:00
4 changed files with 550 additions and 511 deletions

View File

@@ -2,35 +2,22 @@
#include <event2/event.h>
#include <tfe_future.h>
#include <cert.h>
#include <field_stat2.h>
struct ssl_stream;
struct ssl_mgr;
struct ssl_mgr* init_ssl_manager(const char* ini_profile, const char* section);
void destroy_ssl_manager(struct ssl_mgr* mgr);
struct ssl_chello
{
//client hello
int version;
char* sni;
char* cipher_suites;
};
struct ssl_chello* ssl_peek_result_release_chello(future_result_t* result);
void ssl_chello_free(struct ssl_chello* client_hello);
void ssl_async_peek_client_hello(struct future* future, evutil_socket_t fd, struct event_base *evbase);
struct ssl_mgr* ssl_manager_init(const char* ini_profile, const char* section, struct event_base *evbase, void* logger, screen_stat_handle_t* fs);
void ssl_manager_destroy(struct ssl_mgr* mgr);
struct ssl_stream* ssl_upstream_create_result_release_stream(future_result_t* result);
struct bufferevent* ssl_upstream_create_result_release_bev(future_result_t* result);
void ssl_async_upstream_create(struct future* future, struct ssl_mgr* mgr, const struct ssl_chello* chello, evutil_socket_t fd, struct event_base *evbase);
void ssl_async_upstream_create(struct future* f, struct ssl_mgr* mgr, evutil_socket_t fd_upstream, evutil_socket_t fd_downstream, struct event_base *evbase);
struct ssl_stream* ssl_downstream_create_result_release_stream(future_result_t* result);
struct bufferevent* ssl_downstream_create_result_release_bev(future_result_t* result);
void ssl_async_downstream_create(struct future* f, struct ssl_mgr* mgr, struct ssl_stream* upstream, evutil_socket_t fd_downstream, struct event_base *evbase);
struct ssl_stream * ssl_downstream_create(struct ssl_mgr* mgr, struct ssl_chello* hello, struct cert* crt);
void ssl_stream_free_and_close_fd(struct ssl_stream* stream, struct event_base *evbase, evutil_socket_t fd);

View File

@@ -0,0 +1,299 @@
#include <assert.h>
#include <ssl_sess_cache.h>
#include <ssl.h>
#include <MESA_htable.h>
#include <field_stat2.h>
#define SESS_CACHE_NOT_FOUND -1
#define SESS_CACHE_FOUND 0
#define SESS_CACHE_UPDATE_OLD 1
#define SESS_CACHE_ADD_NEW 2
#define SESS_CACHE_INVALID 3
struct asn1_sess
{
unsigned char* buff;
size_t size;
};
struct sess_set_args
{
MESA_htable_handle hash;
struct asn1_sess* new_sess;
};
struct sess_cache
{
enum tfe_conn_dir served_for;
MESA_htable_handle hash;
long long hit_cnt, miss_cnt,del_err;
};
static void ssl_sess_free_serialized(void *data)
{
struct asn1_sess* p=(struct asn1_sess*)data;
free(p->buff);
p->size=0;
free(p);
return;
}
static struct asn1_sess* ssl_sess_serialize(SSL_SESSION *sess)
{
struct asn1_sess* result=ALLOC(struct asn1_sess,1);
result->size = i2d_SSL_SESSION(sess, NULL);
/*When using i2d_SSL_SESSION(), the memory location pointed to by pp must be large enough to hold the binary representation of the session.
There is no known limit on the size of the created ASN1 representation, so the necessary amount of space should be obtained by first calling
i2d_SSL_SESSION() with pp=NULL, and obtain the size needed, then allocate the memory and call i2d_SSL_SESSION() again.*/
result->buff=ALLOC(unsigned char,result->size);
i2d_SSL_SESSION(sess, &(result->buff));
return result;
}
static SSL_SESSION * ssl_sess_deserialize(const struct asn1_sess* asn1)
{
SSL_SESSION *sess=NULL;
sess = d2i_SSL_SESSION(NULL, &(asn1->buff), asn1->size); /* increments asn1 */
return sess;
}
static int ssl_sess_varify_cb(void *data, int eliminate_type)
{
SSL_SESSION *sess=NULL;
int ret=0;
const struct asn1_sess* asn1=(struct asn1_sess*)data;
if(eliminate_type==ELIMINATE_TYPE_NUM)
{
return 1; //direct expired.
}
sess=ssl_sess_deserialize(asn1);
ret=ssl_session_is_valid(sess);
SSL_SESSION_free(sess);
if(ret==0)
{
return 1; //should be expired (deleted).
}
else
{
return 0;
}
}
static long sess_cache_get_cb(void *data, const uchar *key, uint size, void *user_arg)
{
SSL_SESSION *sess=NULL;
int is_valid=0;
if(data==NULL)
{
return SESS_CACHE_NOT_FOUND;
}
const struct asn1_sess* asn1=(struct asn1_sess*)data;
sess=ssl_sess_deserialize(data,asn1);
is_valid=ssl_session_is_valid(sess);
if(is_valid==0)
{
SSL_SESSION_free(sess);
return SESS_CACHE_INVALID;
}
else
{
*(SSL_SESSION **)user_arg=sess;
return SESS_CACHE_FOUND;
}
}
static long sess_cache_set_cb(void *data, const uchar *key, uint size, void *user_arg)
{
struct sess_set_args* args=(struct sess_set_args*)user_arg;
struct asn1_sess* new_asn1=args->new_sess;
struct asn1_sess *cur_asn1=(struct asn1_sess*)data;
int ret=0;
if(cur_asn1!=NULL)
{
free(cur_asn1->buff);
cur_asn1->size=new_asn1->size;
cur_asn1->buff=ALLOC(unsigned char, cur_asn1->size);
memcpy(cur_asn1->buff,new_asn1->buff,cur_asn1->size);
return SESS_CACHE_UPDATE_OLD;
}
else
{
ret=MESA_htable_add(args->hash, key, size, new_asn1);
assert(ret>=0);
return SESS_CACHE_ADD_NEW;
}
}
static int upsess_mk_key(struct sockaddr * addr, socklen_t addr_len, const char* sni, unsigned char** key_buf)
{
size_t key_size=0;
unsigned char* tmp=NULL, p=NULL;
size_t tmp_size;
dynbuf_t tmp, *db;
short port;
size_t snilen;
switch (((struct sockaddr_storage *)addr)->ss_family) {
case AF_INET:
tmp = (unsigned char *)
&((struct sockaddr_in*)addr)->sin_addr;
tmp_size = sizeof(struct in_addr);
port = ((struct sockaddr_in*)addr)->sin_port;
break;
case AF_INET6:
tmp = (unsigned char *)
&((struct sockaddr_in6*)addr)->sin6_addr;
tmp_size = sizeof(struct in6_addr);
port = ((struct sockaddr_in6*)addr)->sin6_port;
break;
default:
//should never happens.
assert(0);
break;
}
snilen = sni ? strlen(sni) : 0;
key_size=tmp_size+sizeof(port)+snilen;
*key_buf=ALLOC(unsigned char, key_size);
p=*key_buff;
memcpy(p,tmp,tmp_size);
p+=tmp_size;
memcpy(p, (char*)&port, sizeof(port));
p+=sizeof(port);
return key_size;
}
void up_session_set(struct sess_cache* cache, struct sockaddr * addr, socklen_t addr_len, const char* sni, SSL_SESSION * sess)
{
char* key=NULL;
int ret=0;
size_t key_size=0;
long cb_ret=0;
void* no_use=NULL;
assert(cache->served_for==CONN_DIR_UPSTREAM);
key_size=upsess_mk_key(addr, addr_len, sni, &key);
struct asn1_sess* asn1=NULL;
asn1=ssl_sess_serialize(sess);
struct sess_set_args set_args;
set_args.hash=cache->hash;
set_args.new_sess=asn1;
no_use=MESA_htable_search_cb(cache->hash, key, key_size, sess_cache_set_cb, &set_args,&cb_ret);
if(cb_ret==SESS_CACHE_UPDATE_OLD)
{
ssl_sess_free_serialized(asn1);
}
free(key);
return;
}
SSL_SESSION* up_session_get(struct sess_cache* cache, struct sockaddr * addr, socklen_t addr_len, const char* sni)
{
SSL_SESSION* sess=NULL;
void* no_use=NULL;
long cb_ret=0;
char* key=NULL;
size_t key_size=0;
assert(cache->served_for==CONN_DIR_UPSTREAM);
key_size=upsess_mk_key(addr, addr_len, sni, &key);
no_use=MESA_htable_search_cb(cache->hash, key, key_size,sess_cache_get_cb, &sess, &cb_ret);
free(key);
key=NULL;
if(cb_ret==1)
{
cache->hit_cnt++;
return sess;
}
else
{
cache->miss_cnt++;
return NULL;
}
}
void down_session_set(struct sess_cache* cache, const SSL_SESSION* sess)
{
unsigned int idlen=0;
struct asn1_sess* asn1=NULL;
long cb_ret=0;
void* no_use=NULL;
int ret=0;
assert(cache->served_for==CONN_DIR_DOWNSTREAM);
asn1=ssl_sess_serialize(sess);
/*
* SSL_SESSION_get_id() returns a pointer to the internal session id value for the session s.
* The length of the id in bytes is stored in *idlen. The length may be 0.
* The caller should not free the returned pointer directly.
*/
const unsigned char* id = SSL_SESSION_get_id(sess, &idlen);
struct sess_set_args set_args;
set_args.hash=cache->hash;
set_args.new_sess=asn1;
no_use=MESA_htable_search_cb(cache->hash, id, (unsigned int)idlen, sess_cache_set_cb, &set_args,&cb_ret);
if(cb_ret==SESS_CACHE_UPDATE_OLD)
{
ssl_sess_free_serialized(asn1);
}
return;
}
SSL_SESSION* down_session_get(struct sess_cache* cache, unsigned char * id, int idlen)
{
SSL_SESSION* sess=NULL;
void* no_use=NULL;
long cb_ret=0;
assert(cache->served_for==CONN_DIR_DOWNSTREAM);
no_use=MESA_htable_search_cb(cache->hash, id, (unsigned int)idlen, sess_cache_get_cb, &sess,&cb_ret);
if(cb_ret==1)
{
cache->hit_cnt++;
return sess;
}
else
{
cache->miss_cnt++;
return NULL;
}
}
void down_session_del(struct sess_cache* cache, const SSL_SESSION* sess)
{
assert(cache->served_for==CONN_DIR_DOWNSTREAM);
unsigned int len=0;
const unsigned char* id = SSL_SESSION_get_id(sess, &len);
int ret=MESA_htable_del(cache->hash, id, len, NULL);
if(ret!=MESA_HTABLE_RET_OK)
{
cache->del_err++;
}
return;
}
struct sess_cache* ssl_sess_cache_create(int slot_size, int expire_seconds, enum tfe_conn_dir served)
{
struct sess_cache* cache=ALLOC(struct sess_cache, 1);
MESA_htable_handle htable=NULL;
int ret=0,max_num=slot_size*4;
htable=MESA_htable_born();
value=0;//no print
ret=MESA_htable_set_opt(htable, MHO_SCREEN_PRINT_CTRL, &(value), sizeof(value));
value=1;//thread safe
ret=MESA_htable_set_opt(htable, MHO_THREAD_SAFE, value, sizeof(value));
assert(ret==0);
value=16;
ret=MESA_htable_set_opt(htable, MHO_MUTEX_NUM, value, sizeof(value));
ret=MESA_htable_set_opt(htable, MHO_HASH_SLOT_SIZE, &(slot_size), sizeof(slot_size));
ret=MESA_htable_set_opt(htable, MHO_HASH_MAX_ELEMENT_NUM, &(max_num), sizeof(max_num));
ret=MESA_htable_set_opt(htable, MHO_EXPIRE_TIME, &(expire_seconds), sizeof(expire_seconds));
value=HASH_ELIMINATE_ALGO_FIFO;
ret=MESA_htable_set_opt(htable, MHO_ELIMIMINATE_TYPE, &(value), sizeof(value));
ret=MESA_htable_set_opt(htable, MHO_CBFUN_DATA_FREE, ssl_sess_free_serialized, sizeof(ssl_sess_free_serialized));
ret=MESA_htable_set_opt(htable, MHO_CBFUN_DATA_EXPIRE_NOTIFY, ssl_sess_varify_cb, sizeof(ssl_sess_varify_cb));
assert(ret==0);
ret=MESA_htable_mature(htable);
assert(ret==0);
cache->hash=htable;
cache->served_for=served;
return cache;
}
void ssl_sess_cache_destroy(struct sess_cache* cache)
{
MESA_htable_destroy(cache->hash, NULL);
cache->hash=NULL;
free(cache);
return;
}

View File

@@ -0,0 +1,15 @@
#pragma once
#include <sys/socket.h>
#include <openssl/ssl.h>
#include <tfe_stream.h>
struct sess_cache;
struct sess_cache* ssl_sess_cache_create(int slot_size, int expire_seconds, enum tfe_conn_dir served);
void ssl_sess_cache_destroy(struct sess_cache* cache);
void up_session_set(struct sess_cache* cache, struct sockaddr * addr, socklen_t addr_len, const char* sni, SSL_SESSION * value)
SSL_SESSION* up_session_get(struct sess_cache* cache, struct sockaddr *addr, socklen_t addr_len, const char* sni);
void down_session_set(struct sess_cache* cache, const SSL_SESSION* sess);
SSL_SESSION* down_session_get(struct sess_cache* cache, unsigned char * id, int idlen);

View File

@@ -27,41 +27,53 @@
#include <tfe_future.h>
#include <stream.h>
#include <cert.h>
#include <ssl_sess_cache.h>
#include <ssl.h>
#include <MESA_htable.h>
#define SSL_EX_DATA_IDX_SSLMGR 0
#define MAX_NET_RETRIES 50
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 sslcomp;
int no_ssl2;
int no_ssl3;
int no_tls10;
int no_tls11;
int no_tls12;
CONST_SSL_METHOD *(*sslmethod)(void); //Parameter of SSL_CTX_new
int sslversion;
char ssl_session_context[8];
MESA_htable_handle down_sess_cache;
MESA_htable_handle up_sess_cache;
char* default_ciphers;
int cache_slot_num;
int sess_expire_seconds;
struct sess_cache* down_sess_cache;
struct sess_cache* up_sess_cache;
char* default_ciphers[TFE_SYMBOL_MAX];
DH * dh;
char * ecdhcurve;
char * crlurl;
uint8_t SSL_MODE_RELEASE_BUFFERS;
void *logger;
};
struct ssl_stream
{
enum tfe_conn_dir dir;
SSL * ssl;
struct ssl_mgr* ref_mgr;
char* sni;
};
struct ssl_chello
{
//client hello
int version;
char* sni;
char* cipher_suites;
};
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
{
@@ -70,63 +82,85 @@ struct ssl_connect_origin_ctx
struct sockaddr addr;
socklen_t addrlen;
};
struct ssl_mgr* init_ssl_manager(const char* ini_profile, const char* section)
/*
* SSL shutdown context.
*/
struct ssl_shutdown_ctx {
struct ssl_stream* s_stream;
struct event_base *evbase;
struct event *ev;
unsigned int retries;
};
static int parse_ssl_version(const char * version_str)
{
struct ssl_mgr* mgr=ALLOC(struct ssl_mgr*, 1);
memcpy(mgr->ssl_session_context,"mesa-tfe",sizeof(mgr->ssl_session_context));
return mgr;
int sslversion=-1;
assert(OPENSSL_VERSION_NUMBER >= 0x10100000L);
/*
* Support for SSLv2 and the corresponding SSLv2_method(),
* SSLv2_server_method() and SSLv2_client_method() functions were
* removed in OpenSSL 1.1.0.
*/
if (!strcmp(version_str, "ssl3"))
{
sslversion = SSL3_VERSION;
}
else if (!strcmp(version_str, "tls10") || !strcmp(version_str, "tls1"))
{
sslversion = TLS1_VERSION;
}
else if (!strcmp(version_str, "tls11"))
{
sslversion = TLS1_1_VERSION;
}
else if (!strcmp(version_str, "tls12"))
{
sslversion = TLS1_2_VERSION;
}
else
{
sslversion =-1;
}
return sslversion;
}
void destroy_ssl_manager(struct ssl_mgr* mgr)
void ssl_manager_destroy(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)
struct ssl_mgr* ssl_manager_init(const char* ini_profile, const char* section, void* logger)
{
}
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;
struct ssl_mgr* mgr=ALLOC(struct ssl_mgr, 1);
int ret=0,value=0;
char version_str[TFE_SYMBOL_MAX];
mgr->logger=logger;
MESA_load_profile_string_def(ini_profile, section, "ssl_version", version_str, sizeof(version_str),"tls12");
mgr->sslversion=parse_ssl_version(version_str);
if(mgr->sslversion<0)
{
TFE_LOG_ERROR(logger, "Unsupported SSL/TLS protocol %s",version_str);
goto error_out;
}
if (copy)
return sess;
SSL_SESSION_free(sess);
return ((void*)-1);
//tfe2a uses SSLv23_method, it was been deprecated and replaced with the TLS_method() in openssl 1.1.0.
mgr->sslmethod=TLS_method;
MESA_load_profile_int_def(ini_profile, section, "ssl_compression", &(mgr->sslcomp), 1);
MESA_load_profile_int_def(ini_profile, section, "no_ssl2", &(mgr->no_ssl2), 1);
MESA_load_profile_int_def(ini_profile, section, "no_ssl3", &(mgr->no_ssl3), 1);
MESA_load_profile_int_def(ini_profile, section, "no_tls10", &(mgr->no_tls10), 1);
MESA_load_profile_int_def(ini_profile, section, "no_tls11", &(mgr->no_tls11), 0);
MESA_load_profile_int_def(ini_profile, section, "no_tls12", &(mgr->no_tls12), 0);
MESA_load_profile_int_def(ini_profile, section, "session_cache_slot_num", &(mgr->cache_slot_num), 4*1024*1024);
MESA_load_profile_int_def(ini_profile, section, "session_cache_slot_num", &(mgr->sess_expire_seconds), 30*60);
mgr->up_sess_cache=ssl_sess_cache_create(mgr->cache_slot_num, mgr->sess_expire_seconds,CONN_DIR_UPSTREAM);
mgr->down_sess_cache=ssl_sess_cache_create(mgr->cache_slot_num, mgr->sess_expire_seconds,CONN_DIR_DOWNSTREAM);
memcpy(mgr->ssl_session_context,"mesa-tfe",sizeof(mgr->ssl_session_context));
return mgr;
error_out:
ssl_manager_destroy(mgr);
return NULL;
}
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)
{
@@ -211,7 +245,7 @@ static void peek_client_hello_cb(evutil_socket_t fd, short what, void * arg)
reason="see no client hello";
goto failed;
}
if(ctx->sni_peek_retries++ > MAX_NET_RETRIES)
{
TFE_LOG_ERROR("Peek failed due to too many retries\n");
@@ -267,7 +301,7 @@ static SSL* upstream_ssl_create(struct ssl_mgr* mgr, const struct ssl_chello* ch
sslctx = SSL_CTX_new(mgr->sslmethod());
sslctx_set_opts(sslctx, mgr);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
@@ -312,11 +346,6 @@ static SSL* upstream_ssl_create(struct ssl_mgr* mgr, const struct ssl_chello* ch
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)
{
@@ -383,7 +412,7 @@ static void ssl_connect_origin_eventcb(struct bufferevent * bev, short events, v
ssl_connect_origin_ctx_free(ctx);
return
}
void ssl_async_upstream_create(struct future* f, struct ssl_mgr* mgr, const struct ssl_chello* chello, evutil_socket_t fd, struct event_base *evbase)
{
struct promise* p=future_to_promise(future);
@@ -394,6 +423,7 @@ void ssl_async_connect_origin(struct future* future, struct ssl_mgr* mgr, const
ret=getpeername(fd, &(ctx->addr), &(ctx->addrlen));
assert(ret==0);
ctx->ssl=ALLOC(struct ssl_stream,1);
ctx->ssl->ref_mgr=mgr;
ctx->ssl->dir=CONN_DIR_UPSTREAM;
ctx->ssl->sni=tfe_strdup(chello->sni);
ctx->ssl->ssl=upstream_ssl_create(mgr, chello, &(ctx->addr), ctx->addrlen);
@@ -403,194 +433,6 @@ void ssl_async_connect_origin(struct future* future, struct ssl_mgr* mgr, const
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;
/*
@@ -601,7 +443,7 @@ out:
* keep a pointer to the object (which we never do here).
*/
static int
static int
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);
@@ -625,9 +467,10 @@ pxy_ossl_sessnew_cb(SSL * ssl, SSL_SESSION * sess)
* OpenSSL calls SSL_SESSION_free() after calling the callback;
* we do not need to free the reference here.
*/
static void
static void
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);
assert(mgr!=NULL);
if (sess)
{
@@ -640,7 +483,7 @@ pxy_ossl_sessremove_cb(UNUSED SSL_CTX * sslctx, SSL_SESSION * sess)
* Called by OpenSSL when a src SSL session is requested by the client.
OPENSSL_VERSION_NUMBER >= 0x10100000L required.
*/
static SSL_SESSION *
static SSL_SESSION *
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);
@@ -654,7 +497,7 @@ pxy_ossl_sessget_cb(UNUSED SSL *ssl, const unsigned char *id, int idlen, int *co
/*
* Set SSL_CTX options that are the same for incoming and outgoing SSL_CTX.
*/
static void
static void
sslctx_set_opts(SSL_CTX * sslctx, struct ssl_mgr* mgr)
{
SSL_CTX_set_options(sslctx, SSL_OP_ALL);
@@ -715,223 +558,6 @@ pxy_sslctx_setoptions(SSL_CTX * sslctx, struct ssl_mgr* mgr)
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.
@@ -942,7 +568,7 @@ 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;
sslctx_set_opts(sslctx, mgr);
//TFE's OPENSSL_VERSION_NUMBER >= 0x10100000L
@@ -953,16 +579,13 @@ static SSL_CTX * down_sslctx_create(struct ssl_mgr* mgr, struct cert* crt)
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_new_cb(sslctx, ossl_sessnew_cb);
SSL_CTX_sess_set_remove_cb(sslctx, ossl_sessremove_cb);
SSL_CTX_sess_set_get_cb(sslctx, ossl_sessget_cb);
SSL_CTX_set_session_cache_mode(sslctx, SSL_SESS_CACHE_SERVER |
SSL_SESS_CACHE_NO_INTERNAL);
SSL_SESS_CACHE_NO_INTERNAL);
SSL_CTX_set_session_id_context(sslctx, (void *)(mgr->ssl_session_context),
sizeof(mgr->ssl_session_context));
#endif /* USE_SSL_SESSION_ID_CONTEXT */
sizeof(mgr->ssl_session_context));
#ifndef OPENSSL_NO_DH
if (mgr->dh)
@@ -1000,12 +623,12 @@ static SSL_CTX * down_sslctx_create(struct ssl_mgr* mgr, struct cert* crt)
}
/*
* Create new SSL context for the incoming connection, based on the original
* destination SSL cert.
/*
* Create new SSL context for the incoming connection, based on the a designate cert
* Returns NULL when openssl error.
*/
*/
void ssl_async_downstream_create(struct future* f, struct ssl_mgr* mgr, struct ssl_chello* hello, evutil_socket_t fd, struct event_base *evbase)
{
struct cert* crt;
struct ssl_stream* p=NULL;
if(crt==NULL)
@@ -1013,6 +636,7 @@ struct ssl_stream * ssl_downstream_create(struct ssl_mgr* mgr, struct ssl_chello
goto error_out;
}
p=ALLOC(struct ssl_stream, 1);
p->ref_mgr=mgr;
p->dir=CONN_DIR_DOWNSTREAM;
//could be optimized by store the sslctx.
SSL_CTX * sslctx = down_sslctx_create(mgr,crt);
@@ -1031,7 +655,7 @@ struct ssl_stream * ssl_downstream_create(struct ssl_mgr* mgr, struct ssl_chello
}
if(mgr->SSL_MODE_RELEASE_BUFFERS==1)
{
/* lower memory footprint for idle connections */
/* 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);
@@ -1040,11 +664,125 @@ error_out:
free(p);
return NULL;
}
/*
* Cleanly shut down an SSL socket. Libevent currently has no support for
* cleanly shutting down an SSL socket so we work around that by using a
* low-level event. This works for recent versions of OpenSSL. OpenSSL
* with the older SSL_shutdown() semantics, not exposing WANT_READ/WRITE
* may or may not work.
*/
static struct ssl_shutdown_ctx * ssl_shutdown_ctx_new(struct ssl_stream* s_stream, struct event_base *evbase)
{
free(stream->sni);
stream->sni=NULL;
{
struct ssl_shutdown_ctx *ctx = ALLOC(struct ssl_shutdown_ctx,1);
ctx->evbase = evbase;
ctx->s_stream = s_stream;
ctx->ev = NULL;
ctx->retries = 0;
return ctx;
}
static void ssl_shutdown_ctx_free(struct ssl_shutdown_ctx *ctx)
{
free(ctx);
}
static void ssl_stream_free(struct ssl_stream* s_stream)
{
SSL_free(s_stream->ssl);
s_stream->ssl=NULL;
free(s_stream->sni);
s_stream->sni=NULL;
s_stream->ref_mgr=NULL
free(s_stream);
return;
}
/*
* The shutdown socket event handler. This is either
* scheduled as a timeout-only event, or as a fd read or
* fd write event, depending on whether SSL_shutdown()
* indicates it needs read or write on the socket.
*/
static void
pxy_ssl_shutdown_cb(evutil_socket_t fd, UNUSED short what, void *arg)
{
struct ssl_shutdown_ctx *ctx = (struct ssl_shutdown_ctx *)arg;
struct timeval retry_delay = {0, 100};
void *logger=ctx->s_stream->ref_mgr->logger;
short want = 0;
int rv=0, sslerr=0;
if (ctx->ev) {
event_free(ctx->ev);
ctx->ev = NULL;
}
/*
* Use the new (post-2008) semantics for SSL_shutdown() on a
* non-blocking socket. SSL_shutdown() returns -1 and WANT_READ
* if the other end's close notify was not received yet, and
* WANT_WRITE it could not write our own close notify.
*
* This is a good collection of recent and relevant documents:
* http://bugs.python.org/issue8108
*/
rv = SSL_shutdown(ctx->s_stream->ssl);
if (rv == 1)
goto complete;
if (rv != -1) {
goto retry;
}
switch ((sslerr = SSL_get_error(ctx->s_stream->ssl, rv))) {
case SSL_ERROR_WANT_READ:
want = EV_READ;
goto retry;
case SSL_ERROR_WANT_WRITE:
want = EV_WRITE;
goto retry;
case SSL_ERROR_ZERO_RETURN:
goto retry;
case SSL_ERROR_SYSCALL:
case SSL_ERROR_SSL:
goto complete;
default:
TFE_LOG_ERROR(logger, "Unhandled SSL_shutdown() "
"error %i. Closing fd.\n", sslerr);
goto complete;
}
goto complete;
retry:
if (ctx->retries++ >= MAX_NET_RETRIES) {
TFE_LOG_ERROR(logger,"Failed to shutdown SSL connection cleanly: "
"Max retries reached. Closing fd.\n");
goto complete;
}
ctx->ev = event_new(ctx->evbase, fd, want, pxy_ssl_shutdown_cb, ctx);
if (ctx->ev) {
event_add(ctx->ev, &retry_delay);
return;
}
TFE_LOG_ERROR(logger,"Failed to shutdown SSL connection cleanly: "
"Cannot create event. Closing fd.\n");
complete:
ssl_stream_free(ctx->s_stream);
evutil_closesocket(fd);
ssl_shutdown_ctx_free(ctx);
}
/*
* Cleanly shutdown an SSL session on file descriptor fd using low-level
* file descriptor readiness events on event base evbase.
* Guarantees that SSL and the corresponding SSL_CTX are freed and the
* socket is closed, eventually, or in the case of fatal errors, immediately.
*/
void ssl_stream_free_and_close_fd(struct ssl_stream* s_stream, struct event_base *evbase, evutil_socket_t fd)
{
struct ssl_shutdown_ctx *sslshutctx=NULL;
sslshutctx = ssl_shutdown_ctx_new(s_stream, evbase);
pxy_ssl_shutdown_cb(fd, 0, sslshutctx);
return;
}