深入理解nginx的https sni机制

news2024/11/18 19:47:58

目录

  • 1. 概述
  • 2. 初识sni
  • 3. nginx的ssl证书配置指令
    • 3.1 ssl_certificate
    • 3.2 ssl_certificate_key
    • 3.3 ssl_password_file
  • 4. nginx源码分析
    • 4.1 给ssl上下文的初始化
    • 4.2 连接初始化
    • 4.3 处理sni回调
    • 4.2 动态证书的加载
  • 5. 总结

阅读姊妹篇: 深入理解nginx的https alpn机制

1. 概述

  SNI(Server Name Indication)是一种TLS(Transport Layer Security)协议的扩展,用于在建立加密连接时指定服务器的主机名。在使用单个IP地址和端口提供多个域名的服务时,SNI是非常有用的。
  当客户端发起TLS握手时,它会发送一个包含所请求主机名的扩展,这样服务器就可以根据这个主机名选择合适的证书来完成握手。这使得服务器能够在同一IP地址和端口上为多个域名提供加密连接,而不需要为每个域名分配一个独立的IP地址。
  对于HTTPS网站来说,SNI是至关重要的,因为它允许服务器在同一IP地址上为多个域名提供加密连接,不需要为每个域名单独部署一台服务器,从而降低了运维成本并提高了灵活性。
  在使用SNI时,服务器端必须能够根据客户端发送的SNI信息来选择正确的证书进行握手。通常,服务器端配置会包含多个虚拟主机的证书信息,以便根据收到的SNI信息选择正确的证书来完成握手。
  总的来说,SNI允许客户端在TLS握手期间指定所请求的主机名,从而使服务器能够根据主机名选择正确的证书,实现一个IP地址上多个域名的加密连接。

  本文基于nginx,对sni的实现原理进行深入的分析。

2. 初识sni

  有图有真相,先上一张抓包图,如下图:
在这里插入图片描述

  在ssl握手的第一个报文ClientHello中我们可以看到server_name的扩展信息,里面包含了当前请求的网站域名www.test.com。
  当服务器收到这个报文后,将会解析出server_name扩展信息,这样子就可以在还没有收到客户端发送的HTTP报文前,即SSL握手阶段就提前知道了客户端需要访问的域名,服务器从而可以从容地在握手阶段选择绑定了该域名的SSL证书,来完成整个握手的过程。
  需要强调一下的是,每个从CA申请下来的证书是会绑定域名的,SSL证书可以绑定一个或者多个域名,甚至是泛域名,这样子当浏览器在用https访问网站的时候,服务器会将配置的证书发送给浏览器,浏览器会根据拿到的证书进行检查,包括检查当前访问的域名是不是在证书中列出的域名列表中,如果不是的话,浏览器就会显示不安全网站的警告,甚至拒绝用户访问该网站。

3. nginx的ssl证书配置指令

  nginx和ssl相关的配置指令很多,但是和证书配置相关的指令主要包括ssl_certificate、ssl_certificate_key、 ssl_password_file这三个。下面分别来说明一下:

3.1 ssl_certificate

语  法:	ssl_certificate file;
默认值:	—
上下文:	http, server

  这个指令用于给一个虚拟主机配置一个PEM格式的证书文件,如果除了主证书外还需要指定证书链中的中间证书,它们应该按照以下顺序在同一个文件中指定:首先是主证书,然后是中间证书。一个以 PEM 格式的私钥也可以放在同一个文件中。
  从nginx 1.10.0版本开始,可以配置多个ssl_certificate以便加载不同类型的证书,如RSA and ECDSA等。
  从nginx 1.15.9版本开始,如果openssl版本大于等于1.0.2, 那么nginx可以支持证书文件名嵌入动态变量,这样子可以很将配置书写成下面的格式,如:

ssl_certificate     $ssl_server_name.crt;
ssl_certificate_key $ssl_server_name.key;

  从而能够自动在握手的时候加载用户请求所对应域名的证书文件,方便了运维配置工作,当然这样子配置可能有一定的性能上的损失。
  nginx也支持直接将证书文件的内容用data:$variable的形式来设置,而这个variable的值可以用nginx插件来设置,这样子就完全不需要文件了,便于程序根据实际需要更加灵活第动态加载证书。

3.2 ssl_certificate_key

语  法:	ssl_certificate_key file;
默认值:	—
上下文:	http, server

  这个指令用于给一个虚拟主机配置证书文件对应的证书私钥,与ssl_certificate需要一一对应。同样支持文件名嵌入动态变量,和data:$variable方式加载证书,另外还支持engine:name:id格式的配置,用来让nginx从openssl的某个engine中获取指定id的证书私钥。

3.3 ssl_password_file

语  法:	ssl_password_file file;
默认值:	—
上下文:	http, server

   配置一个用于解密证书私钥的密码本文件,密码本文件每个密码一行,nginx依次尝试解密。当然,如果证书私钥没有加密,那么ssl_password_file是可以不进行配置的。

4. nginx源码分析

4.1 给ssl上下文的初始化

  ssl上下文的初始化是在ngx_http_ssl_merge_srv_conf函数中进行的,这个时候配置文件中的证书、密钥、加密文件的配置已经读取到了,本函数在这里执行main和server级别的配置的合并,然后就是创建ssl上下文,最后加载证书到ssl上下文了,源码如下:

    if (ngx_ssl_ciphers(cf, &conf->ssl, &conf->ciphers,
                        conf->prefer_server_ciphers)
        != NGX_OK)
    {
   
        return NGX_CONF_ERROR;
    }

	/* 如果ssl_certificate和ssl_certificate_key配置的有动态变量,
	   会进行变量的解析工作 */
    if (ngx_http_ssl_compile_certificates(cf, conf) != NGX_OK) {
   
        return NGX_CONF_ERROR;
    }

	/* conf->certificate_values不为NULL
	   表示ssl_certificate和ssl_certificate_key配置含有动态变量  */
    if (conf->certificate_values) {
   

#ifdef SSL_R_CERT_CB_ERROR

        /* install callback to lookup certificates */
        /* 向ssl底层注册一个证书选择的回调函数 */
        SSL_CTX_set_cert_cb(conf->ssl.ctx, ngx_http_ssl_certificate, conf);

#else
        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
                      "variables in "
                      "\"ssl_certificate\" and \"ssl_certificate_key\" "
                      "directives are not supported on this platform");
        return NGX_CONF_ERROR;
#endif

    } else if (conf->certificates) {
   

        /* configure certificates */
        /* 配置的证书是静态文件且不包含动态变量,那么直接将证书加载到ssl上下文 */
        if (ngx_ssl_certificates(cf, &conf->ssl, conf->certificates,
                                 conf->certificate_keys, conf->passwords)
            != NGX_OK)
        {
   
            return NGX_CONF_ERROR;
        }
    }
......

  上面的代码会判断配置的证书是否静态文件,如果是静态文件则在这个阶段就直接将证书加载到ssl上下文中,因为这个阶段信息已经很清楚了,后续就不需要加载了;如果不是静态文件,那么这个阶段是没办法知道要加载的证书到底是什么内容的,要等到最终进行ssl握手的时候才能知晓,所以nginx通过SSL_CTX_set_cert_cb注册了一个回调函数ngx_http_ssl_certificate,最终在需要加载证书的时候就会回调这个函数来获取真正的证书内容。

  当然,还有一个需要关注的是下面的代码,这个代码也是在ngx_http_ssl_merge_srv_conf函数中执行的,源码如下:

#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME

    if (SSL_CTX_set_tlsext_servername_callback(conf->ssl.ctx,
                                               ngx_http_ssl_servername)
        == 0)
    {
   
        ngx_log_error(NGX_LOG_WARN, cf->log, 0,
            "nginx was built with SNI support, however, now it is linked "
            "dynamically to an OpenSSL library which has no tlsext support, "
            "therefore SNI is not available");
    }

#endif

  这段代码注册了sni的回调函数ngx_http_ssl_servername到ssl上下文中。

4.2 连接初始化

  在4.1节中所述的ssl上下文准备好以后,ssl连接当然是还没有建立的,只能说仍然只是停留在配置阶段,那么接下去可以想到客户端发起了tcp连接,nginx接受了这个连接,就需要开始对这个连接进行初始化,连接的初始化过程是由ngx_http_init_connection函数来完成的。那么如果开启了https,就会执行如下代码:

#if (NGX_HTTP_SSL)
    {
   
    ngx_http_ssl_srv_conf_t  *sscf;

    sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module);

    if (sscf->enable || hc->addr_conf->ssl) {
   
        hc->ssl = 1;
        c->log->action = "SSL handshaking";
        rev->handler = ngx_http_ssl_handshake;
    }
    }
#endif

  这段代码给当前连接的读事件设置了一个回调函数,即ngx_http_ssl_handshake函数,它用来进行ssl的握手操作。那么当nginx从这个连接上收到请求数据的时候就会开始执行ssl握手操作。在ngx_http_ssl_handshake函数中,有以下这段代码:

	if (ngx_ssl_create_connection(&sscf->ssl, c, NGX_SSL_BUFFER)
		!= NGX_OK)
	{
   
		ngx_http_close_connection(c);
		return;
	}

  这段代码用之前启动阶段准备好的ssl上下文和当前的socket连接来创建一个新的ssl连接,这样子就将当前的socket连接和ssl上下文关联起来了。后面就是真正的ssl握手操作了,在ngx_http_ssl_handshake代码里有:

    rc = ngx_ssl_handshake(c);

  在ngx_ssl_handshake函数里面会发起异步的ssl握手操作,这里略过。

4.3 处理sni回调

  在握手期间,ssl底层逻辑会解析ClientHello数据报文,发现有sni数据后,就回调前面设置好的ngx_http_ssl_servername函数了。下面来分析一下ngx_http_ssl_servername函数的实现:

int
ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg)
{
   
#if defined(T_INGRESS_SHARED_MEMORY_PB) && OPENSSL_VERSION_NUMBER >= 0x10101000L
    return SSL_TLSEXT_ERR_OK;
#endif

    ngx_int_t                  rc;
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1488272.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【在巴厘岛学点印尼语】日常篇

BINTANG BIR 槟棠啤酒 今天不写代码,在巴厘岛休养,顺便聊点印尼语。 印尼语,Bahasa Indonesia,是印度尼西亚的官方语言,也即印尼化的马来语廖内方言,其变种包括 爪哇语(岛民方言) 等…

可视化大屏实现屏幕自适应和自动全屏的实现

前言 在可视化大屏项目中,屏幕适配是绕不过去的一个问题(ps:如果知道大屏展示的屏幕是固定的,当我没说)。这里简单介绍通过 css的transform属性 里面的 scal() 实现常规屏幕适配。 常规屏幕: 1366 * 768…

【学习心得】爬虫JS逆向通解思路

我希望能总结一个涵盖大部分爬虫逆向问题的固定思路,在这个思路框架下可以很高效的进行逆向爬虫开发。目前我仍在总结中,下面的通解思路尚不完善,还望各位读者见谅。 一、第一步:明确反爬手段 反爬手段可以分为几个大类 &#…

Tabby-全网最详细讲解

1.Tabby简介 Tabby是一个无限可定制的跨平台终端应用程序,适用于local shells、serial、SSH和Telnet的连接。 Tabby是基于TypeScript开发的终端模拟器,可用于Linux、Windows和Mac OS系统。 Tabby (前身是 Terminus) 是一个可高度配置的终端模拟器和 SSH …

如何使用支付宝沙箱环境本地配置模拟支付并实现公网远程访问【内网穿透】

文章目录 前言1. 下载当面付demo2. 修改配置文件3. 打包成web服务4. 局域网测试5. 内网穿透6. 测试公网访问7. 配置二级子域名8. 测试使用固定二级子域名访问 前言 在沙箱环境调试支付SDK的时候,往往沙箱环境部署在本地,局限性大,在沙箱环境…

青少年软件编程(Python)等级考试试卷(一级)2020年3月

青少年软件编程(Python)等级考试试卷(一级)2020年3月 第 1 题 【单选题】 运行下方代码段,输出的是( )。 print("a"*3) A :a3 B :3a C :a a a D :aaa 正确答案:D 试题解析 第 2 题 【单选题】 下…

MySQL基础-----SQL语句之DDL数据定义语句

目录 前言 开启登录数据库 一、数据库操作 1.查询所有数据库 2.切换使用数据库 3.查询当前使用的数据库 4.创建数据库 创建一个hello数据库, 使用数据库默认的字符集。 创建一个itheima数据库,并且指定字符集 5.删除数据库 二、表操作 1.查询当前数据库所有…

深入理解C语言:开发属于你的三子棋小游戏

三子棋 1. 前言2. 准备工作3. 使用二维数组存储下棋的数据4. 初始化棋盘为全空格5. 打印棋盘6. 玩家下棋7. 电脑下棋8. 判断输赢9. 效果展示10. 完整代码 1. 前言 大家好,我是努力学习游泳的鱼,今天我们会用C语言实现三子棋。所谓三子棋,就是…

Progressive Widening

下面的解释来源于论文《Monte Carlo Tree Search With Iteratively Refining State Abstractions》,因为这篇论文的重点不是Progressive Widening,所以就不全文学习了,只摘抄其中关于Progressive Widening的部分。 Progressive Widening&…

shell文本处理工具-shell三剑客1

shell脚本常用基础命令2 shell脚本常用基础命令 shell脚本常用基础命令2一、grep用法二、sed用法2.1p参数 (显示)n参数(只显示处理过的行) 文本处理三剑客:grep sed awk 一、grep用法 grep -E egrep (扩展搜索正文表…

【C/C++随笔】static 的用法和作用

「前言」所有文章已经分类好,放心食用 「归属专栏」C语言 | C嘎嘎 「主页链接」个人主页 「笔者」枫叶先生(fy) static 的用法和作用??? static作用: 作用1修改存储方式:用 static 修饰的变量存储在静态区…

牛客练习赛122

D:圆 正着求删除的最小代价不好做,采用逆向思维,求选择一些不相交的线段使得构成一个圆的代价尽量大,最后答案就是所有线段权值之和减去最大代价。 那么如何求这个最大代价呢?显然区间DP 老套路:破环成链&#xff0…

一文读懂DDoS,分享防御DDoS攻击的几大有效方法

DDoS攻击是目前最常见的网络攻击方式之一。其见效快、成本低的特点,使它深受不法分子的喜爱。每39秒就会发生一次新的网络攻击,全球每天大约发生23000次DDoS攻击。对于未受保护的企业来说,每次DDoS攻击的平均成本为20万美元。可见部署DDoS防御…

LabVIEW齿轮传动健康状态静电在线监测

LabVIEW齿轮传动健康状态静电在线监测 随着工业自动化的不断发展,齿轮传动作为最常见的机械传动方式之一,在各种机械设备中发挥着至关重要的作用。然而,齿轮在长期运行过程中易受到磨损、变形等因素影响,进而影响整个机械系统的稳…

二维码门楼牌管理系统技术服务:门牌编设规则详解

文章目录 前言一、门牌编设规则解读二、区间编号与分段编号策略三、多出入口建筑物的门牌编设 前言 随着城市化的快速推进,门楼牌管理成为城市管理中不可或缺的一环。二维码门楼牌管理系统的引入,不仅提升了管理的效率,也为市民提供了更为便…

poi-tl表格行循环(自定义复杂表头)

输入模板 注:集合使用{{xx}}进行标识(在其需要循环的上一行进行标识),[xx]中的内容表示集合中对象属性 public static void main(String[] args) throws IOException {Map<String, Object> map new HashMap<>();LoopRowTableRenderPolicy policy new LoopRowTab…

FPGA高端项目:FPGA基于GS2971的SDI视频接收+图像缩放,提供3套工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐本博已有的 SDI 编解码方案本方案的SDI接收转HDMI输出应用本方案的SDI接收纯verilog图像缩放纯verilog多路视频拼接应用本方案的SDI接收HLS图像缩放HLS多路视频拼接应用本方案的SDI接收HLS动态字符叠加输出应用本方案的SDI接收HLS多路视频…

【Web - 框架 - Vue】随笔 - 通过CDN的方式使用VUE 2.0和Element UI

通过CDN的方式使用VUE 2.0和Element UI - 快速上手 VUE 网址 https://cdn.bootcdn.net/ajax/libs/vue/2.7.16/vue.js源码 https://download.csdn.net/download/HIGK_365/88815507测试 代码 <!DOCTYPE html> <html lang"en"> <head><meta …

【论文阅读-PRIVGUARD】Day4:3节

3 PRIVANALYZER&#xff1a;强制执行隐私政策的静态分析 本节介绍PRIVANALYZER&#xff0c;这是一个用于强制执行由PRIVGUARD追踪的隐私政策的静态分析器**。我们首先回顾LEGALEASE政策语言&#xff0c;我们使用它来正式编码政策&#xff0c;然后描述如何静态地强制执行它们**…

【24最新版PythonPycharm安装教程】小白保姆级别安装教程

今天&#xff0c;我就来教大家一下&#xff0c;如何去安装Python&#xff01; 需要博主打包好的一键激活版Pycharm&&Python也可扫下方直接获取 ​ 1 了解Python Python是一种面向对象的解释型计算机程序设计语言&#xff0c;由荷兰人Guido van Rossum于1989年发明&…