OpenSSL-SNI

一、什么是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     }
…
    发送的样式如下图所示:

3.4 Client处理server_name扩展

    Client在处理ServerHello中如果发现了server_name扩展,则会检查自己之前是否发送过这个扩展。如果发送过则将扩展的名字记录在会话中:

                
  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值