瀏覽代碼

tls: server side SNI support

- adapted from the patch of Klaus Darilion for Kamailio v1.5, which was
  lost with the ser integration
- this is a partial patch of the original one, adding support for SNI
  when accepting TLS connections
Daniel-Constantin Mierla 10 年之前
父節點
當前提交
cfade98e19
共有 6 個文件被更改,包括 136 次插入10 次删除
  1. 2 0
      modules/tls/tls_config.c
  2. 8 0
      modules/tls/tls_config.h
  3. 114 6
      modules/tls/tls_domain.c
  4. 3 1
      modules/tls/tls_domain.h
  5. 2 0
      modules/tls/tls_init.c
  6. 7 3
      modules/tls/tls_server.c

+ 2 - 0
modules/tls/tls_config.c

@@ -160,6 +160,7 @@ static cfg_option_t options[] = {
 	{"cipher_list",         .f = cfg_parse_str_opt, .flags = CFG_STR_SHMMEM},
 	{"ca_list",             .f = cfg_parse_str_opt, .flags = CFG_STR_SHMMEM},
 	{"crl",                 .f = cfg_parse_str_opt, .flags = CFG_STR_SHMMEM},
+	{"server_name",         .f = cfg_parse_str_opt, .flags = CFG_STR_SHMMEM},
 	{0}
 };
 
@@ -183,6 +184,7 @@ static void update_opt_variables(void)
 	options[12].param = &domain->cipher_list;
 	options[13].param = &domain->ca_file;
 	options[14].param = &domain->crl_file;
+	options[15].param = &domain->server_name;
 }
 
 

+ 8 - 0
modules/tls/tls_config.h

@@ -29,6 +29,14 @@
 #include "../../str.h"
 #include "tls_domain.h"
 
+#include <openssl/ssl.h>
+
+#ifndef OPENSSL_NO_TLSEXT
+#ifndef SSL_CTRL_SET_TLSEXT_HOSTNAME
+#define OPENSSL_NO_TLSEXT
+#endif
+#endif
+
 tls_domains_cfg_t* tls_load_config(str* filename);
 
 /*

+ 114 - 6
modules/tls/tls_domain.c

@@ -35,6 +35,7 @@
 #include "../../pt.h"
 #include "../../cfg/cfg.h"
 #include "../../dprint.h"
+#include "tls_config.h"
 #include "tls_server.h"
 #include "tls_util.h"
 #include "tls_mod.h"
@@ -878,6 +879,71 @@ static int tls_ssl_ctx_set_read_ahead(SSL_CTX* ctx, long val, void* unused)
 	return 0;
 }
 
+
+#ifndef OPENSSL_NO_TLSEXT
+
+/**
+ * @brief SNI callback function
+ *
+ * callback on server_name -> trigger context switch if a TLS domain
+ * for the server_name is found (checks socket too) */
+static int tls_server_name_cb(SSL *ssl, int *ad, void *private)
+{
+    tls_domain_t *orig_domain, *new_domain;
+	str server_name;
+
+	orig_domain = (tls_domain_t*)private;
+	server_name.s = (char*)SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+	if (server_name.s)  {
+		LM_DBG("received server_name (TLS extension): '%s'\n", server_name.s);
+	} else {
+		LM_DBG("SSL_get_servername returned NULL: return SSL_TLSEXT_ERR_NOACK\n");
+		return SSL_TLSEXT_ERR_NOACK;
+	}
+
+	server_name.len = strlen(server_name.s);
+
+	new_domain = tls_lookup_cfg(*tls_domains_cfg, TLS_DOMAIN_SRV,
+			&orig_domain->ip, orig_domain->port, &server_name);
+	if (new_domain==NULL) {
+		LM_DBG("TLS domain for socket [%s:%d] and server_name='%s' "
+			"not found\n", ip_addr2a(&orig_domain->ip),
+			orig_domain->port, server_name.s);
+		/* we do not perform SSL_CTX switching, thus the default server domain
+		   for this socket (or the default server domain) will be used. */
+		return SSL_TLSEXT_ERR_ALERT_WARNING;
+	}
+
+	LM_DBG("TLS cfg domain selected for received server name [%s]:"
+		" socket [%s:%d] server name='%s' -"
+		" switching SSL CTX to %p dom %p%s\n",
+		server_name.s, ip_addr2a(&new_domain->ip),
+		new_domain->port, ZSW(new_domain->server_name.s),
+		new_domain->ctx[process_no], new_domain,
+		(new_domain->type & TLS_DOMAIN_DEF)?" (default)":"");
+	SSL_set_SSL_CTX(ssl, new_domain->ctx[process_no]);
+	/* SSL_set_SSL_CTX only sets the correct certificate parameters, but does
+	   set the proper verify options. Thus this will be done manually! */
+
+	SSL_set_options(ssl, SSL_CTX_get_options(ssl->ctx));
+	if ((SSL_get_verify_mode(ssl) == SSL_VERIFY_NONE) ||
+				(SSL_num_renegotiations(ssl) == 0)) {
+		/*
+		 * Only initialize the verification settings from the ctx
+		 * if they are not yet set, or if we're called when a new
+		 * SSL connection is set up (num_renegotiations == 0).
+		 * Otherwise, we would possibly reset a per-directory
+		 * configuration which was put into effect by ssl_hook_access.
+		 */
+		SSL_set_verify(ssl, SSL_CTX_get_verify_mode(ssl->ctx),
+			SSL_CTX_get_verify_callback(ssl->ctx));
+	}
+
+	return SSL_TLSEXT_ERR_OK;
+}
+#endif
+
+
 /**
  * @brief Initialize all domain attributes from default domains if necessary
  * @param d initialized TLS domain
@@ -915,8 +981,36 @@ static int fix_domain(tls_domain_t* d, tls_domain_t* def)
 		if(d->method>TLS_USE_TLSvRANGE) {
 			SSL_CTX_set_options(d->ctx[i], (long)ssl_methods[d->method - 1]);
 		}
+#ifndef OPENSSL_NO_TLSEXT
+		/*
+		* check server domains for server_name extension and register
+		* callback function
+		*/
+		if ((d->type & TLS_DOMAIN_SRV) && d->server_name.len>0) {
+			if (!SSL_CTX_set_tlsext_servername_callback(d->ctx[i], tls_server_name_cb)) {
+				LM_ERR("register server_name callback handler for socket "
+					"[%s:%d], server_name='%s' failed for proc %d\n",
+					ip_addr2a(&d->ip), d->port, d->server_name.s, i);
+				return -1;
+			}
+			if (!SSL_CTX_set_tlsext_servername_arg(d->ctx[i], d)) {
+				LM_ERR("register server_name callback handler data for socket "
+					"[%s:%d], server_name='%s' failed for proc %d\n",
+					ip_addr2a(&d->ip), d->port, d->server_name.s, i);
+				return -1;
+			}
+		}
+#endif
 	}
-	
+
+#ifndef OPENSSL_NO_TLSEXT
+	if ((d->type & TLS_DOMAIN_SRV) && d->server_name.len>0) {
+		LM_NOTICE("registered server_name callback handler for socket "
+			"[%s:%d], server_name='%s' ...\n", ip_addr2a(&d->ip), d->port,
+			d->server_name.s);
+	}
+#endif
+
 	if (load_cert(d) < 0) return -1;
 	if (load_ca_list(d) < 0) return -1;
 	if (load_crl(d) < 0) return -1;
@@ -1195,7 +1289,7 @@ tls_domains_cfg_t* tls_new_cfg(void)
  * @return found configuration or default, if not found
  */
 tls_domain_t* tls_lookup_cfg(tls_domains_cfg_t* cfg, int type,
-								struct ip_addr* ip, unsigned short port)
+		struct ip_addr* ip, unsigned short port, str *sname)
 {
 	tls_domain_t *p;
 
@@ -1208,8 +1302,17 @@ tls_domain_t* tls_lookup_cfg(tls_domains_cfg_t* cfg, int type,
 	}
 
 	while (p) {
-		if ((p->port == port) && ip_addr_cmp(&p->ip, ip))
-			return p;
+		if ((p->port == port) && ip_addr_cmp(&p->ip, ip)) {
+			if(sname && sname->len>0) {
+				if(p->server_name.len==sname->len
+					&& strncasecmp(p->server_name.s, sname->s, sname->len)==0) {
+					LM_DBG("socket+server_name based TLS server domain found\n");
+					return p;
+				}
+			} else {
+				return p;
+			}
+		}
 		p = p->next;
 	}
 
@@ -1238,8 +1341,13 @@ static int domain_exists(tls_domains_cfg_t* cfg, tls_domain_t* d)
 	}
 
 	while (p) {
-		if ((p->port == d->port) && ip_addr_cmp(&p->ip, &d->ip))
-			return 1;
+		if ((p->port == d->port) && ip_addr_cmp(&p->ip, &d->ip)) {
+			if(p->server_name.len==0) {
+				LM_WARN("another tls domain with same address was defined"
+						" and no server name provided\n");
+				return 1;
+			}
+		}
 		p = p->next;
 	}
 

+ 3 - 1
modules/tls/tls_domain.h

@@ -105,6 +105,7 @@ typedef struct tls_domain {
 	str cipher_list;
 	enum tls_method method;
 	str crl_file;
+	str server_name;
 	struct tls_domain* next;
 } tls_domain_t;
 
@@ -189,10 +190,11 @@ int tls_fix_domains_cfg(tls_domains_cfg_t* cfg, tls_domain_t* srv_defaults,
  * @param type type of configuration
  * @param ip IP for configuration
  * @param port port for configuration
+ * @param sname server name
  * @return found configuration or default, if not found
  */
 tls_domain_t* tls_lookup_cfg(tls_domains_cfg_t* cfg, int type,
-								struct ip_addr* ip, unsigned short port);
+				struct ip_addr* ip, unsigned short port, str *sname);
 
 
 /**

+ 2 - 0
modules/tls/tls_init.c

@@ -138,6 +138,8 @@ const SSL_METHOD* ssl_methods[TLS_METHOD_MAX];
 #define NULL_GRACE_PERIOD 10U
 */
 
+
+
 inline static char* buf_append(char* buf, char* end, char* str, int str_len)
 {
 	if ( (buf+str_len)<end){

+ 7 - 3
modules/tls/tls_server.c

@@ -151,6 +151,8 @@ static int tls_complete_init(struct tcp_connection* c)
 	      * count immediately.
 	      */
 
+	LM_DBG("completing tls connection initialization\n");
+
 	lock_get(tls_domains_cfg_lock);
 	cfg = *tls_domains_cfg;
 
@@ -164,17 +166,19 @@ static int tls_complete_init(struct tcp_connection* c)
 	if (c->flags & F_CONN_PASSIVE) {
 		state=S_TLS_ACCEPTING;
 		dom = tls_lookup_cfg(cfg, TLS_DOMAIN_SRV,
-								&c->rcv.dst_ip, c->rcv.dst_port);
+								&c->rcv.dst_ip, c->rcv.dst_port, 0);
 	} else {
 		state=S_TLS_CONNECTING;
 		dom = tls_lookup_cfg(cfg, TLS_DOMAIN_CLI,
-								&c->rcv.dst_ip, c->rcv.dst_port);
+								&c->rcv.dst_ip, c->rcv.dst_port, 0);
 	}
 	if (unlikely(c->state<0)) {
 		BUG("Invalid connection (state %d)\n", c->state);
 		goto error;
 	}
-	DBG("Using TLS domain %s\n", tls_domain_str(dom));
+	DBG("Using initial TLS domain %s (dom %p ctx %p sn [%s])\n",
+			tls_domain_str(dom), dom, dom->ctx[process_no],
+			ZSW(dom->server_name.s));
 
 	data = (struct tls_extra_data*)shm_malloc(sizeof(struct tls_extra_data));
 	if (!data) {