深入理解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);#elsengx_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_HOSTNAMEif (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 >= 0x10101000Lreturn SSL_TLSEXT_ERR_OK;
#endifngx_int_t                  rc;

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

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

相关文章

Vue+SpringBoot打造音乐偏好度推荐系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、系统设计2.1 功能模块设计2.1.1 音乐档案模块2.1.2 我的喜好模块2.1.3 每日推荐模块2.1.4 通知公告模块 2.2 用例图设计2.3 实体类设计2.4 数据库设计 三、系统展示3.1 登录注册3.2 音乐档案模块3.3 音乐每日推荐模块3.4 通知公告模…

javase_进阶 day8 递归,异常

递归 递归介绍 方法直接或者间接调用本身注意:递归如果没有控制好终止,会出现递归死循环,导致栈内存溢出现象一些算法题的实现, 都需要使用递归 public class RecursionDemo1 {/*递归介绍: 方法直接或者间接调用本身*/public static void …

外包干了6个月,技术退步明显。。。。。

先说一下自己的情况,本科生,2019年我通过校招踏入了重庆一家软件公司,开始了我的职业生涯。那时的我,满怀热血和憧憬,期待着在这个行业中闯出一片天地。然而,随着时间的推移,我发现自己逐渐陷入…

Dockerfile(1) - FROM 指令详解

FROM 指明当前的镜像基于哪个镜像构建dockerfile 必须以 FROM 开头&#xff0c;除了 ARG 命令可以在 FROM 前面 FROM [--platform<platform>] <image> [AS <name>]FROM [--platform<platform>] <image>[:<tag>] [AS <name>]FROM […

搭建独立节点通常涉及哪些步骤

1.了解独立节点搭建的基础概念和原理。这包括理解独立节点搭建的含义&#xff0c;即通过云服务器等方式单独搭建自己的网站&#xff0c;以 确保网站的专属性和自主性&#xff0c;并更好地控制数据流动和安全性。1 2.选择服务器类型和配置。这包括根据需求选择合适的服务器&a…

shardingsphere 集成springboot【水平分表】

创建sharding_sphere数据库 在数据库中创建两张表&#xff0c;t_order_1和t_order_2 分片规则&#xff1a;如果订单编号是偶数添加到t_order_1,如果是奇数添加到t_order_2 创建实体类 public class Order { private Integer id; private Integer orderType; private Int…

Java+SpringBoot+Vue+MySQL:员工健康管理技术新组合

✍✍计算机毕业编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java、…

mysql字节长度限制报错处理方法

报错如下&#xff1a; ERROR 1118 (42000) at line 274: Row size too large (> 8126). Changing some columns to TEXT or BLOB may help. In current row format, BLOB pr efix of 0 bytes is stored inline 或者 ERROR 1118 (42000) at line 1240: Row size too large. T…

TCP的三次握手和四次挥手 | 查看网络状态

三次握手和四次挥手是在计算机网络中用于建立和终止TCP连接的协议。这两个过程是TCP协议的重要组成部分&#xff0c;确保数据的可靠传输。 三次握手指的是在客户端和服务器之间建立连接时的步骤。具体流程如下&#xff1a; 客户端向服务器发送一个连接请求报文段&#xff08;…

Git教程-Git的基本使用

Git是一个强大的分布式版本控制系统&#xff0c;它不仅用于跟踪代码的变化&#xff0c;还能够协调多个开发者之间的工作。在软件开发过程中&#xff0c;Git被广泛应用于协作开发、版本管理和代码追踪等方面。以下是一个详细的Git教程&#xff0c;我们将深入探讨Git的基本概念和…

React 模态框的设计(六)Draggable的整合

前一节课中漏了一个知识点&#xff0c;当内容很长时需要滚动&#xff0c;这个滚动条是很影响美观的。在MacOS下的还能忍&#xff0c;win系统下简直不能看。如何让长内容能滚动又不显示滚动条呢&#xff0c;我尝试过很多办法&#xff0c;最终下面这个方法目前来说是最完美的。我…

Mysql DATETIME与TIMESTAMP的区别

TIMESTAMP的取值范围小&#xff0c;并且TIMESTAMP类型的日期时间在存储时会将当前时区的日期时间值转换为时间标准时间值&#xff0c;检索时再转换回当前时区的日期时间值。 而DATETIME则只能反映出插入时当地的时区&#xff0c;其他时区的人查看数据必然会有误差的。 DATETI…

数据结构——lesson4带头双向循环链表实现

前言✨✨ &#x1f4a5;个人主页&#xff1a;大耳朵土土垚-CSDN博客 &#x1f4a5; 所属专栏&#xff1a;数据结构学习笔记​​​​​​ &#x1f4a5;双链表与单链表的区分&#xff1a;单链表介绍与实现 &#x1f4a5;对于malloc函数有疑问的:动态内存函数介绍 感谢大家的观看…

tomcat安装步骤流程

安装tomcat是基于安装java的基础上的 JAVA 举例说明&#xff1a; 关闭防火墙 下载java [rootlocalhost ~]#yum install java -y rootlocalhost ~]#yum install epel-release.noarch -y [rootlocalhost ~]#yum provides */javac [rootlocalhost data]#yum install java-1.8.0-o…

半监督学习理解

半监督 少量有标注&#xff0c;大量无标注 1.三个假设&#xff1a; (1)连续性/平滑性假设&#xff1a;相近的数据点可能有相同的标签 (2)集群假设&#xff1a;在分类问题中&#xff0c;数据往往被组织成高密度的集群&#xff0c;同一集群的数据点可能具有相同的标签。因此&…

C/C++常见的字符串操作函数

2 字符串操作 ​ 由双引号&#xff08;Double Quote&#xff09;引起来的一串字符称为字符串字面值&#xff08;String Literal&#xff09;&#xff0c;或者简称字符串。注&#xff1a;字符串的结束标志是一个 \0 的转义字符。在计算字符串长度的时候 \0 是结束标志&#xff…

6.1 deeplabv3+的pth模型转换为rknn模型

和yolov5的pth模型转换为rknn模型类似&#xff0c;deeplabv3的pth模型转为rknn模型的步骤是&#xff1a; pth------>onnx-------->rknn 1.pth转为onnx 代码如下&#xff1a; #!/usr/bin/env python3 # -*- coding: utf-8 -*- # by [jackhanyuan](https://github.com/…

实现一个线程安全的单例模式

单例模式 单例模式能保证某个类在程序中只存在唯⼀⼀份实例,⽽不会创建出多个实例 某个类,在一个类,只应该创建出一个实例,使用单例模式,就可以对咱们的代码进行一个更严格的校验和检查 单例模式具体的实现⽅式有很多.最常⻅的是"饿汉"和"懒汉"两种单例…

Linux之定时任务01

简介 Linux定时任务是一种可执行的命令或者脚本,在特定的时间或者时间间隔下自动执行。通过在系统中预设一些需要执行的任务,可以让Linux定时任务自动执行并完成这些任务。定时任务可以用于自动备份、系统清理、监控、自动化维护等任务。 一、apache服务器监控 [rootlocalhost…

代码随想录算法训练营day27

题目&#xff1a;93.复原IP地址、78.子集、90.子集II 参考链接&#xff1a;代码随想录 93.复原IP地址 思路&#xff1a;本题的思路和上题切割回文串类似&#xff0c;也是先要写一个判断函数&#xff0c;然后一个个切割。对返回条件&#xff0c;如果路径长度已经为4&#xff…