一、什么是SNI?
SNI是Server Name Indication的缩写,是为了解决一个服务器使用多个域名和证书的SSL/TLS扩展。它允许客户端在发起SSL握手请求时(客户端发出ClientHello消息中)提交请求的HostName信息,使得服务器能够切换到正确的域并返回相应的证书。在SNI出现之前,HostName信息只存在于HTTP请求中,但SSL/TLS层无法获知这一信息。通过将HostName的信息加入到SNI扩展中,SSL/TLS允许服务器使用一个IP为不同的域名提供不同的证书,从而能够与使用同一个IP的多个“虚拟主机”更方便地建立安全连接。
二、RFC中SNI的定义
RFC 6066《Transport Layer Security (TLS) Extensions: Extension Definitions》对SNI扩展做了详细的定义。要点如下:1) Client需要在ClientHello中包含一个名为”server_name”的扩展,这个扩展的”extension_data”域中需要包含”ServerNameList”;
2) ServerNameList中包含了多个HostName及其类型(name_type),但所有HostName的name_type不能相同(早期的RFC规范允许一个name_type多个HostName,但 实际上当前的client实现只发送一个HostName,而且client不一定知道server选择了哪个HostName,因此禁止一个name_type多个HostName);
3) HostName中包含的是server的完全合格的DNS主机名,且HostName中不允许包含IPv4或IPv6地址;
4) 如果server收到的ClientHello中带有”server_name”扩展,它也应该在ServerHello中包含一个”server_name”扩展,其中的”extension_data”域应为空;
5) 当执行会话恢复时,clinet应该在ClientHello中包含与上次会话相同的”server_name”扩展。如果扩展中包含的name与上次的不同,server必须拒绝恢复会话。恢复会话时server必须不能在ServerHello中包含”server_name”扩展;
6) 如果一个应用程序使用应用层协议协商了一个server name然后升级到TLS,并且发送了”server_name”扩展,这个扩展中必须包含与在应用层协议中所协商的相同的server name。如果这个server name成功应用在了TLS会话中,client不应该在应用层尝试请求一个不同的server name。
三、OpenSSL(基于OpenSSL-1.1.0f)与SNI
3.1 Client设置server_name扩展
用户可以通过SSL_set_tlsext_host_name(s,name)函数来设置ClientHello中的Server Name:246 # define SSL_set_tlsext_host_name(s,name) \
247 SSL_ctrl(s,SSL_CTRL_SET_TLSEXT_HOSTNAME,TLSEXT_NAMETYPE_host_name,(char *)name)
1670 long SSL_ctrl(SSL *s, int cmd, long larg, void *parg)
1671 {
1672 long l;
1673
1674 switch (cmd) {
…
1747 default:
1748 return (s->method->ssl_ctrl(s, cmd, larg, parg));
1749 }
1750 }
对于TLS_client_method()和TLS_server_method(),s->method->ssl_ctrl指向ssl3_ctrl:
2883 long ssl3_ctrl(SSL *s, int cmd, long larg, void *parg)
2884 {
2885 int ret = 0;
2886
2887 switch (cmd) {
…
2961 case SSL_CTRL_SET_TLSEXT_HOSTNAME:
2962 if (larg == TLSEXT_NAMETYPE_host_name) {
2963 size_t len;
2964
2965 OPENSSL_free(s->tlsext_hostname);
2966 s->tlsext_hostname = NULL;
2967
2968 ret = 1;
2969 if (parg == NULL)
2970 break;
2971 len = strlen((char *)parg);
2972 if (len == 0 || len > TLSEXT_MAXLEN_host_name) {
2973 SSLerr(SSL_F_SSL3_CTRL, SSL_R_SSL3_EXT_INVALID_SERVERNAME);
2974 return 0;
2975 }
2976 if ((s->tlsext_hostname = OPENSSL_strdup((char *)parg)) == NULL) {
2977 SSLerr(SSL_F_SSL3_CTRL, ERR_R_INTERNAL_ERROR);
2978 return 0;
2979 }
2980 } else {
2981 SSLerr(SSL_F_SSL3_CTRL, SSL_R_SSL3_EXT_INVALID_SERVERNAME_TYPE);
2982 return 0;
2983 }
2984 break;
…
可见SSL_set_tlsext_host_name(s,name)函数最终将name保存到s->tlsext_hostname上,在构建ClientHello扩展时将其发送出去:
968 unsigned char *ssl_add_clienthello_tlsext(SSL *s, unsigned char *buf,
969 unsigned char *limit, int *al)
970 {
…
1027 if (s->tlsext_hostname != NULL) {
1028 /* Add TLS extension servername to the Client Hello message */
1029 size_t size_str;
1030
1031 /*-
1032 * check for enough space.
1033 * 4 for the servername type and extension length
1034 * 2 for servernamelist length
1035 * 1 for the hostname type
1036 * 2 for hostname length
1037 * + hostname length
1038 */
1039 size_str = strlen(s->tlsext_hostname);
1040 if (CHECKLEN(ret, 9 + size_str, limit))
1041 return NULL;
1042
1043 /* extension type and length */
1044 s2n(TLSEXT_TYPE_server_name, ret);
1045 s2n(size_str + 5, ret);
1046
1047 /* length of servername list */
1048 s2n(size_str + 3, ret);
1049
1050 /* hostname type, length and hostname */
1051 *(ret++) = (unsigned char)TLSEXT_NAMETYPE_host_name;
1052 s2n(size_str, ret);
1053 memcpy(ret, s->tlsext_hostname, size_str);
1054 ret += size_str;
1055 }
…
发送的ClientHello中的扩展信息如图:
3.2 Server设置servername_callback
为了根据ClientHello中的servername返回相应的证书以及进行其它相关处理,server需要设置callback函数来完成这些功能:279 # define SSL_CTX_set_tlsext_servername_callback(ctx, cb) \
280 SSL_CTX_callback_ctrl(ctx,SSL_CTRL_SET_TLSEXT_SERVERNAME_CB,(void (*)(void))cb)
1882 long SSL_CTX_callback_ctrl(SSL_CTX *ctx, int cmd, void (*fp) (void))
1883 {
1884 switch (cmd) {
1885 case SSL_CTRL_SET_MSG_CALLBACK:
1886 ctx->msg_callback = (void (*)
1887 (int write_p, int version, int content_type,
1888 const void *buf, size_t len, SSL *ssl,
1889 void *arg))(fp);
1890 return 1;
1891
1892 default:
1893 return (ctx->method->ssl_ctx_callback_ctrl(ctx, cmd, fp));
1894 }
1895 }
3488 long ssl3_ctx_callback_ctrl(SSL_CTX *ctx, int cmd, void (*fp) (void))
3489 {
3490 switch (cmd) {
…
3498 case SSL_CTRL_SET_TLSEXT_SERVERNAME_CB:
3499 ctx->tlsext_servername_callback = (int (*)(SSL *, int *, void *))fp;
3500 break;
…
这里设置的callback函数会在Server处理server_name扩展时调用。
通常server在回调函数被调用时都需要知道ClientHello中包含的Host Name的内容,这可以通过SSL_get_servername()函数来实现:
2081 const char *SSL_get_servername(const SSL *s, const int type)
2082 {
2083 if (type != TLSEXT_NAMETYPE_host_name)
2084 return NULL;
2085
2086 return s->session && !s->tlsext_hostname ?
2087 s->session->tlsext_hostname : s->tlsext_hostname;
2088 }
3.3 Server处理server_name扩展
Server在检查ClientHello时,如果发现了”server_name”扩展则会对其进行解析:1890 static int ssl_scan_clienthello_tlsext(SSL *s, PACKET *pkt, int *al)
1891 {
…
1986 else if (type == TLSEXT_TYPE_server_name) {
1987 unsigned int servname_type;
1988 PACKET sni, hostname;
1989
1990 if (!PACKET_as_length_prefixed_2(&extension, &sni)
1991 /* ServerNameList must be at least 1 byte long. */
1992 || PACKET_remaining(&sni) == 0) {
1993 return 0;
1994 }
1995
1996 /*
1997 * Although the server_name extension was intended to be
1998 * extensible to new name types, RFC 4366 defined the
1999 * syntax inextensibility and OpenSSL 1.0.x parses it as
2000 * such.
2001 * RFC 6066 corrected the mistake but adding new name types
2002 * is nevertheless no longer feasible, so act as if no other
2003 * SNI types can exist, to simplify parsing.
2004 *
2005 * Also note that the RFC permits only one SNI value per type,
2006 * i.e., we can only have a single hostname.
2007 */
2008 if (!PACKET_get_1(&sni, &servname_type)
2009 || servname_type != TLSEXT_NAMETYPE_host_name
2010 || !PACKET_as_length_prefixed_2(&sni, &hostname)) {
2011 return 0;
2012 }
2013
2014 if (!s->hit) {
2015 if (PACKET_remaining(&hostname) > TLSEXT_MAXLEN_host_name) {
2016 *al = TLS1_AD_UNRECOGNIZED_NAME;
2017 return 0;
2018 }
2019
2020 if (PACKET_contains_zero_byte(&hostname)) {
2021 *al = TLS1_AD_UNRECOGNIZED_NAME;
2022 return 0;
2023 }
2024
2025 if (!PACKET_strndup(&hostname, &s->session->tlsext_hostname)) {
2026 *al = TLS1_AD_INTERNAL_ERROR;
2027 return 0;
2028 }
2029
2030 s->servername_done = 1;
2031 } else {
2032 /*
2033 * TODO(openssl-team): if the SNI doesn't match, we MUST
2034 * fall back to a full handshake.
2035 */
2036 s->servername_done = s->session->tlsext_hostname
2037 && PACKET_equal(&hostname, s->session->tlsext_hostname,
2038 strlen(s->session->tlsext_hostname));
2039 }
2040 }
…
2009行:OpenSSL中的Server Name Type只有TLSEXT_NAMETYPE_host_name一种。
2014-2039:如果不是出于会话恢复过程中,则将hostname解析到s->session->tlsext_hostname;否则对比扩展中的hostname和上次保存的hostname,不一致则发起全新的握手(不允许恢复会话)。
在解析完server_name扩展之后,OpenSSL会在ssl_check_clienthello_tlsext_early()函数中调用server设置的回调函数:
2319 int ssl_parse_clienthello_tlsext(SSL *s, PACKET *pkt)
2320 {
2321 int al = -1;
2322 custom_ext_init(&s->cert->srv_ext);
2323 if (ssl_scan_clienthello_tlsext(s, pkt, &al) <= 0) {
2324 ssl3_send_alert(s, SSL3_AL_FATAL, al);
2325 return 0;
2326 }
2327 if (ssl_check_clienthello_tlsext_early(s) <= 0) {
2328 SSLerr(SSL_F_SSL_PARSE_CLIENTHELLO_TLSEXT, SSL_R_CLIENTHELLO_TLSEXT);
2329 return 0;
2330 }
2331 return 1;
2332 }
2670 static int ssl_check_clienthello_tlsext_early(SSL *s)
2671 {
2672 int ret = SSL_TLSEXT_ERR_NOACK;
2673 int al = SSL_AD_UNRECOGNIZED_NAME;
2674
2675 #ifndef OPENSSL_NO_EC
2676 /*
2677 * The handling of the ECPointFormats extension is done elsewhere, namely
2678 * in ssl3_choose_cipher in s3_lib.c.
2679 */
2680 /*
2681 * The handling of the EllipticCurves extension is done elsewhere, namely
2682 * in ssl3_choose_cipher in s3_lib.c.
2683 */
2684 #endif
2685
2686 if (s->ctx != NULL && s->ctx->tlsext_servername_callback != 0)
2687 ret =
2688 s->ctx->tlsext_servername_callback(s, &al,
2689 s->ctx->tlsext_servername_arg);
2690 else if (s->session_ctx != NULL
2691 && s->session_ctx->tlsext_servername_callback != 0)
2692 ret =
2693 s->session_ctx->tlsext_servername_callback(s, &al,
2694 s->
2695 session_ctx->tlsext_servername_arg);
2696
2697 switch (ret) {
2698 case SSL_TLSEXT_ERR_ALERT_FATAL:
2699 ssl3_send_alert(s, SSL3_AL_FATAL, al);
2700 return -1;
2701
2702 case SSL_TLSEXT_ERR_ALERT_WARNING:
2703 ssl3_send_alert(s, SSL3_AL_WARNING, al);
2704 return 1;
2705
2706 case SSL_TLSEXT_ERR_NOACK:
2707 s->servername_done = 0;
2708 default:
2709 return 1;
2710 }
2711 }
如果call_back函数返回的是SSL_TLSEXT_ERR_OK,则OpenSSL会在ServerHello中添加一个内容为空的server_name扩展:
1449 unsigned char *ssl_add_serverhello_tlsext(SSL *s, unsigned char *buf,
1450 unsigned char *limit, int *al)
1451 {
…
1500 if (!s->hit && s->servername_done == 1
1501 && s->session->tlsext_hostname != NULL) {
1502 /*-
1503 * check for enough space.
1504 * 4 bytes for the server name type and extension length
1505 */
1506 if (CHECKLEN(ret, 4, limit))
1507 return NULL;
1508
1509 s2n(TLSEXT_TYPE_server_name, ret);
1510 s2n(0, ret);
1511 }
…
发送的样式如下图所示: