深入理解nginx的https sni机制

目录

  • 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;
    

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

FreeRTOS 其它知识点

目录 一、低功耗Tickless模式 1、低功耗Tickless模式的引入 2、Tickless 具体实现 二、空闲任务 1、空闲任务相关知识点 2、钩子函数 3、空闲任务钩子函数 三、使用RTOS的好处 一、低功耗Tickless模式 1、低功耗Tickless模式的引入 FreeRTOS 的系统时钟是由滴答定时器中…

数字孪生与智慧交通的融合发展:推动交通行业数字化转型,构建智慧城市新生态

随着信息技术的快速发展和城市化进程的深入推进,交通行业正面临着前所未有的机遇与挑战。传统的交通管理模式已难以满足日益增长的交通需求,而数字化转型则成为了推动交通行业创新发展的必由之路。数字孪生技术作为一种前沿的信息技术手段,为…

LIS(最长上升子序列, 合唱队形)

最长上升子序列 直接使用动态规划: 这个题目的关键就是在于我们选定一个数,然后利用这个数作为标准和这个数之前的所有数进行比较,如果比前面某一个数要大,那么就需要将这数自己本身的现存的最长长度与比较出来的数的最长加一&am…

【iOS ARKit】RealityKit 同步机制

协作 Session 可以很方便地实现多用户之间的AR体验实时共享,但开发者需要自行负责并确保AR场景的完整性,自行负责虚拟物体的创建与销毁。为简化同步操作,RealityKit 内建了同步机制,RealityKit 同步机制基于 Multipeer Connectivi…

Java核心卷一 · 笔记04

C++ type_info 类的使用 在 C++ 中,type_info 类是一个标准库提供的用于运行时类型信息的类。它定义在 <typeinfo> 头文件中,并用于获取和比较类型信息。下面是一些使用 type_info 类的常见操作示例: 包含头文件:#include <typeinfo>使用 typeid 运算符获取类…

安全防御(第六次作业)

攻击可能只是一个点&#xff0c; 防御需要全方面进行 IAE引擎 DFI和DPI技术 --- 深度检测技术 DPI --- 深度包检测技术 --- 主要针对完整的数据包&#xff08;数据包分片&#xff0c;分段需要重组&#xff09; &#xff0c;之后对 数据包的内容进行识别。&#xff08;应用层&a…

18 SpringMVC实战

18 SpringMVC实战 1. 课程介绍2. Spring Task定时任务1. 课程介绍 2. Spring Task定时任务 package com.imooc.reader.task

LSS 论文及代码详解:Lift, Splat, Shoot:

文章目录 1. 相关概念1.1 什么叫做BEV自底向上方法1.2 BEV网格2. 自底向上方法框架-LSS2.1 视锥点云和Lift操作2.1.1 视锥点云的空间位置2.1.2 视锥点云的特征(Context)2.2 BEV Pillar和Splat操作2.3 Shoot: Motion Planning2.4 完整的pipeline2.5 cumsum_trick(): 池化累积求…

LINUX基础培训二十七之shell标准输入、输出、错误

一、Shell 输入/输出重定向 大多数 UNIX 系统命令从你的终端接受输入并将所产生的输出发送回​​到您的终端。一个命令通常从一个叫标准输入的地方读取输入&#xff0c;默认情况下&#xff0c;这恰好是你的终端。同样&#xff0c;一个命令通常将其输出写入到标准输出&#xff…

数电学习笔记——逻辑代数的基本定理

目录 一、带入定理 二、反演定理 三、对偶定理 一、带入定理 在任何一个包含变量A的逻辑等式中&#xff0c;若以另外一个逻辑式代入式中所有A的位置&#xff0c;则等式仍然成立。 例1&#xff1a;&#xff08;AB&#xff09;AB 将&#xff08;BC&#xff09;带入等式中所…

Jlink Segger工具软件的应用(如何连接)

一、Jlink Commander的如何连接 1、点击打开“Jlink Commander” 2、输入“connect” 3、根据提示输入“&#xff1f;”。 此处是选择MCU 内核类型 4、此时jink commander 会提示选择对应的内核&#xff0c;如“图F5.1”。根据内核类型进行选择。 SWM1xx系列、SWM2xx系列…

做活动和会议直播,为什么要多个媒体平台同步直播?

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 多个媒体平台同步直播活动和会议的原因主要有以下几点&#xff1a; 扩大观众覆盖面&#xff1a;不同的媒体平台拥有各自独特的用户群体&#xff0c;通过在多个媒体平台同步直播&#xff…

2024年3月阿里云服务器价格下调折扣表(附优惠价格表)

阿里云服务器ECS等核心产品价格全线下调&#xff0c;最高幅度达55%&#xff0c;2024年3月1日生效&#xff0c;针对ECS部分在售产品的官网折扣价、ECS计算型节省计划进行调整&#xff0c;生效后&#xff0c;基于官网折扣价的新购和续费&#xff0c;将按照新的价格进行计费。阿里…

IDEA-DeBug理论与实践

文章目录 01_Debug简介和意义02_IDEA中的Debug步骤03_跳转到当前代码执行的行04_步过调试的使用05_步入调试的使用06_强制步入调试的使用07_步出调试的使用08_回退断点调试的使用09_运行到光标处10_计算表达式11_条件断点12_多线程调试 在软件开发中&#xff0c;IDEA&#xff0…

物联网技术助力智慧城市安全建设:构建全方位、智能化的安全防护体系

一、引言 随着城市化进程的加速和信息技术的迅猛发展&#xff0c;智慧城市已成为现代城市发展的重要方向。在智慧城市建设中&#xff0c;安全是不可或缺的一环。物联网技术的快速发展为智慧城市安全建设提供了有力支持&#xff0c;通过构建全方位、智能化的安全防护体系&#…

SpringMVC01、回顾MVC

1、回顾MVC 1.1、什么是MVC MVC是模型(Model)、视图(View)、控制器(Controller)的简写&#xff0c;是一种软件设计规范。是将业务逻辑、数据、显示分离的方法来组织代码。MVC主要作用是降低了视图与业务逻辑间的双向偶合。MVC不是一种设计模式&#xff0c;MVC是一种架构模式。…

Qt槽函数不响应的原因总结

Qt专栏&#xff1a;http://t.csdnimg.cn/LE2Lx 目录 1.问题 2.原因 2.1.没有继承QObject&#xff0c;声明Q_OBJECT宏 2.2.信号槽参数不匹配 2.3.信号函数未声明为 signals 2.4.访问权限 2.5.注意connect的位置&#xff0c;信号在创建信号槽连接前使用&#xff0c;则无法…

前端 JS 经典:Content-type 详解

1. 什么是 Content-Type Content-Type 是 HTTP 协议中的一个请求头或响应头字段&#xff0c;用于指示发送或接收的实体的媒体类型&#xff0c;告诉服务器或客户端如何解析和处理请求或响应的主体部分。 2. Content-Type 的构成 Content-Type 由两部分组成&#xff1a;媒体类型…

(python)多线程

前言 Python 多线程的应用场景通常是在需要同时执行多个 I/O 密集型任务时&#xff0c;以提高程序的效率和性能。 多线程应用场景 网络爬虫&#xff1a;当需要从多个网站获取数据时&#xff0c;使用多线程可以同时发起多个 HTTP 请求&#xff0c;以加快数据获取速度。 数据库操…

新闻稿软文投放推广发布需要注意什么

在全球化的背景下&#xff0c;各国之间的联系与互动变得越来越频繁。无论是经济、文化还是科技领域&#xff0c;各国之间的交流和合作都在不断加深。而在这个信息爆炸的互联网时代&#xff0c;人们获取信息的主要途径也逐渐转向了网络。 在这种情况下&#xff0c;软文推广成为…