Server Name Indication(SNI),HTTP/TLS握手过程解析

news2024/9/21 10:34:49

Server Name Indication(SNI)是一种TLS扩展,用于在TLS握手过程中传递服务器的域名信息。在未使用SNI之前,客户端在建立TLS连接时只能发送单个IP地址,并且服务器无法知道客户端请求的具体域名。这导致服务器需要使用默认证书进行握手,无法正确选择合适的证书。

使用SNI扩展后,客户端在发送ClientHello消息时会包含所请求的服务器的域名。服务器根据该域名来选择对应的证书进行握手,从而实现了多个域名共享同一个IP地址并使用不同证书的能力。

SNI对于虚拟主机或者CDN等场景特别有用,因为这些场景下,多个网站可能共享同一个IP地址。通过使用SNI,服务器能够正确地选择与域名匹配的证书,提高了安全性和灵活性。

SNI产生解决什么问题

Server Name Indication(SNI)的产生背景与互联网发展和IPv4地址短缺有关。

在互联网发展初期,每个网站通常拥有自己独立的IP地址。但随着网站数量的迅速增长和IPv4地址资源的有限,IP地址短缺问题变得日益严重。

为了解决IPv4地址短缺问题,一种解决方案是引入虚拟主机技术。虚拟主机是指在同一台物理服务器上托管多个不同域名的网站。通过共享同一个IP地址,多个网站能够减少IP地址的使用,并降低运维成本。

然而,这种虚拟主机技术在使用TLS(Transport Layer Security)协议时遇到了问题。TLS协议是用于加密和保护网络通信的协议,它在握手过程中需要使用正确的证书来验证服务器身份。但在使用虚拟主机时,服务器无法根据传统的握手方式正确选择合适的证书,因为无法获知客户端请求的具体域名。

为了解决这个问题,SNI扩展被引入TLS协议中。它允许客户端在握手时发送所请求的服务器域名信息,使服务器能够正确选择并使用对应的证书,实现了多个域名共享同一个IP地址并使用不同证书的能力。SNI的引入为虚拟主机和CDN等场景提供了更好的支持,进一步促进了IPv4地址资源的有效利用。

抓包分析http握手过程

触发http请求

curl www.baidu.com

tcpdump抓包:

tcpdump -i any port 80 -nnnvvvv -w http.pcap


HTTP (Hypertext Transfer Protocol) 的握手过程如下:

  • 1.建立TCP连接:客户端通过向服务器发送一个SYN包来发起与服务器的TCP连接。

  • 2.服务器回应:服务器接收到客户端的SYN包后,返回一个SYN-ACK包,表示同意建立连接。

  • 3.客户端确认:客户端接收到服务器的SYN-ACK包后,发送一个ACK包给服务器,表示连接已建立成功。

  • 4.发送HTTP请求:客户端与服务器建立了TCP连接后,客户端可以发送HTTP请求。HTTP请求包括HTTP方法(GET, POST等)、URI(Uniform Resource Identifier)和HTTP协议版本等信息。

  • 5.服务器响应:服务器接收到HTTP请求后,根据请求的内容进行处理,并返回HTTP响应。HTTP响应包括响应状态码(例如200表示成功,404表示未找到等)、响应头和响应体等信息。

  • 6.关闭连接:客户端接收到服务器的HTTP响应后,根据需要继续发送其他请求,或者关闭TCP连接。

这是一个简化的HTTP握手过程,实际上还有更多的细节和可选项,例如在建立TCP连接时可能需要进行三次握手以确保连接的可靠性,还有可能使用持久连接(Keep-Alive)机制来减少握手的次数等。HTTP握手过程中的这些步骤保证了客户端和服务器之间的通信顺利进行,并确保数据在网络传输时的可靠性和完整性。
抓包分析TLS握手过程

触发https请求

curl -k --insecure "https://www.baidu.com"
tcpdump -i any port 443 -nnnvvvv -w https.pcap

WireShark抓包验证SNI:

TLS握手过程是一个复杂的过程,包括以下步骤:

  • 1.客户端发送ClientHello报文:客户端发送一个ClientHello报文,该报文包含客户端支持的协议版本、会话ID、密码组和压缩方法。客户端提交的ClientHello所包含的密码组是客户支持的密码算法列表(按优先级降序排列),压缩方法是客户支持的压缩方法列表。
  • 2.服务器返回ServerHello报文:服务器发送一个ServerHello报文,该报文包含服务器建议的协议版本、服务器选择的密码组和压缩方法,以及两个随机数:ClientHello.random和ServerHello.random。
  • 3.服务器返回CA证书和请求客户端证书:服务器发送自己的证书(Certificate),Certificate 报文包含一个X.509证书或者一条证书链。除了匿名DH之外的密钥交换方法都需要发送Certificate报文。如果服务器需要被认证的话,服务器会发送自己的证书。同时,服务器可能会发送一个ServerKeyExchange报文,例如当服务器没有自己的证书或者证书仅用于签名时。
  • 4.客户端提交证书并发送ClientKeyExchange报文:客户端将自己的证书上报给服务器,同时会发送一个ClientKeyExchange报文,该报文包含密钥协商后的秘钥给服务器。
  • 5.客户端验证服务器的证书:客户端会验证服务器的证书的有效性。如果服务器的证书是有效的,客户端会发送一个ChangeCipherSpec报文和一个EncryptedHandshake消息。
  • 6.服务器发送ChangeCipherSpec报文和EncryptedHandshake消息:服务器也会发送ChangeCipherSpec报文和EncryptedHandshake消息,表示握手协商已经完成。
  • 7.应用层面交互:完成握手协商后,客户端和服务器会开始进行应用层面的交互,通过之前协商的秘钥来加密通信内容。

以上就是TLS握手过程的详细步骤。

Linux C/C++解析 Server Name Indication(SNI)字段

这是一个类似tcpdump的程序,用于在实时或捕获的流量中打印TLS SNI和HTTP/1.1主机字段。

char *tls_ContentType(uint8_t n)
{
	switch (n) {
		/* 20*/
		case SSL3_RT_CHANGE_CIPHER_SPEC:
			return "change_cipher_spec";
		/* 21 */
		case SSL3_RT_ALERT:
			return "alert";
		/* 22 */
		case SSL3_RT_HANDSHAKE:
			return "handshake";
		/* 23 */
		case SSL3_RT_APPLICATION_DATA:
			return "application_data";
		default:
			return "UNKNOWN";
	}

	return "";
}

char *tls_AlertLevel(uint8_t n)
{
	switch(n) {
		/* 1 */
		case SSL3_AL_WARNING:
			return "warning";
		/* 2 */
		case SSL3_AL_FATAL:
			return "fatal";
		default:
			return "UNKNOWN";
	}

	return "";
}

char *tls_AlertDescription(uint8_t n)
{
	switch(n) {
		/* 0 */
		case SSL3_AD_CLOSE_NOTIFY:
			return "close_notify";
		/* 10 */
		case SSL3_AD_UNEXPECTED_MESSAGE:
			return "unexpected_message";
		/* 20 */
		case SSL3_AD_BAD_RECORD_MAC:
			return "bad_record_mac";
		case 21:
			return "decryption_failed_RESERVED";
		case 22:
			return "record_overflow";
		case 30:
			return "decompression_failure";
		case 40:
			return "handshake_failure";
		case 41:
			return "no_certificate_RESERVED";
		case 42:
			return "bad_certificate";
		case 43:
			return "unsupported_certificate";
		case 44:
			return "certificate_revoked";
		case 45:
			return "certificate_expired";
		case 46:
			return "certificate_unknown";
		case 47:
			return "illegal_parameter";
		case 48:
			return "unknown_ca";
		case 49:
			return "access_denied";
		case 50:
			return "decode_error";
		case 51:
			return "decrypt_error";
		default:
			return "UNKNOWN";
	}

	return "";
}

char *tls_HandshakeType(uint8_t n)
{
	switch(n) {
		/* 0 */
		case SSL3_MT_HELLO_REQUEST:
			return "hello_request";
		/* 1 */
		case SSL3_MT_CLIENT_HELLO:
			return "client_hello";
		/* 2 */
		case SSL3_MT_SERVER_HELLO:
			return "server_hello";
		/* 11 */
		case SSL3_MT_CERTIFICATE:
			return "certificate";
		/* 12 */
		case SSL3_MT_SERVER_KEY_EXCHANGE:
			return "server_key_exchange";
		/* 13 */
		case SSL3_MT_CERTIFICATE_REQUEST:
			return "certificate_request";
		/* 14 */
		case SSL3_MT_SERVER_DONE:
			return "server_hello_done";
		/* 15 */
		case SSL3_MT_CERTIFICATE_VERIFY:
			return "certificate_verify";
		/* 16 */
		case SSL3_MT_CLIENT_KEY_EXCHANGE:
			return "client_key_exchange";
		/* 20 */
		case SSL3_MT_FINISHED:
			return "finished";
		default:
			return "UNKNOWN";
	}

	return "";
}
...
int tls_set_callback_handshake_clienthello_servername(
	int (*handler)(uint8_t *, uint16_t));
int tls_process_Handshake_ClientHello_Extensions_ServerName();
int tls_process_Handshake_ClientHello_Extensions();
int tls_process_Handshake_ClientHello();
uint32_t tls_process_record(uint8_t *payload, uint32_t payload_length);
int http_set_callback_request_host(int (*handler)(
	uint8_t *host_name, uint16_t host_name_length));
int http_init();
uint16_t http_process_request(uint8_t *payload, uint16_t payload_length);
void http_cleanup();
int sni_handler (uint8_t *host_name, uint16_t host_name_length);

int main (int argc, char *argv[])
{
...

	while ((i = getopt(argc, argv, "hf:pi:r:w:")) != -1) {
		switch(i) {
			case 'h':
				fprintf(stderr,
					"Use: %s [-h] [-f bpf] [-p] -i interface [-w dump.pcap]\n", argv[0]);
				fprintf(stderr,
					"Use: %s [-h] [-f bpf] [-p] -r trce.pcap [-w dump.pcap]\n", argv[0]);
				return -1;
				break;
			case 'f':
				bpf_s = optarg;
				opt_flags |= OPT_BPF;
				break;
			case 'p':
				opt_flags |= OPT_PROMISCUOUS;
				break;
			case 'i':
				device_name = optarg;
				opt_flags |= OPT_DEVICE;
				break;
			case 'r':
				trace_fname = optarg;
				opt_flags |= OPT_TRACE;
				break;
			case 'w':
				dump_fname = optarg;
				opt_flags |= OPT_DUMP;
				break;
			default:
				break;
		}
	}

...

	fprintf(stdout, "[*] PID: %u\n", getpid());

	if (opt_flags & OPT_DEVICE) {
		fprintf(stdout, "[*] Device: '%s'\n", device_name);
		fprintf(stdout, "[*] Promiscuous: %d\n", PROMISCUOUS);

		if (!(pcap_handle =
			pcap_open_live(device_name, SNAPLEN, PROMISCUOUS, PCAP_TIMEOUT,
			errbuf))) {
			fprintf(stderr, "[FATAL] %s\n", errbuf);
			return -1;
		}
	}

	if (opt_flags & OPT_TRACE) {
		fprintf(stdout, "[*] Trace: '%s'\n", trace_fname);

		if (!(pcap_handle =
			pcap_open_offline(trace_fname, errbuf))) {
			fprintf(stderr, "[FATAL] %s\n", errbuf);
			return -1;
		}
	}

...
	if (!(opt_flags & OPT_BPF)) {
		bpf_s = bpf_default;
		opt_flags |= OPT_BPF;
	}

	fprintf(stdout, "[*] BPF: '%s'\n", bpf_s);

	if (pcap_compile(pcap_handle, &bpf, BPF, BPF_OPTIMIZE,
		PCAP_NETMASK_UNKNOWN) == -1) {
		fprintf(stderr, "[FATAL] Couldn't parse filter. %s\n",
			pcap_geterr(pcap_handle));
		pcap_close(pcap_handle);
		return -1;
	}

	if (pcap_setfilter(pcap_handle, &bpf) == -1) {
		fprintf(stderr, "[FATAL] Couldn't install filter. %s\n",
			pcap_geterr(pcap_handle));
		pcap_close(pcap_handle);
		return -1;
	}

	pcap_freecode(&bpf);

	pcap_dumper_handle = NULL;

	if (opt_flags & OPT_DUMP) {
		fprintf(stdout, "[*] Dump: '%s'\n", dump_fname);

		if (!(pcap_dumper_handle = pcap_dump_open(pcap_handle, dump_fname))) {
			fprintf(stderr, "[WARNING] Couldn't create dump file. %s\n",
				pcap_geterr(pcap_handle));
		}
	}
...

	tls_set_callback_handshake_clienthello_servername(&sni_handler);
	http_set_callback_request_host(&sni_handler);

	http_init();

	act.sa_handler = signal_handler;
	sigemptyset (&act.sa_mask);
	act.sa_flags = 0;

	if (sigaction(SIGINT, &act, NULL)) {
		perror("sigaction");
		fprintf(stderr,
			"[WARNING] Failed to set signal handler for SIGINT.\n");
	}

	if (sigaction(SIGTERM, &act, NULL)) {
		perror("sigaction");
		fprintf(stderr,
			"[WARNING] Failed to set signal handler for SIGTERM.\n");
	}

	if (sigaction(SIGSEGV, &act, NULL)) {
		perror("sigaction");
		fprintf(stderr,
			"[WARNING] Failed to set signal handler for SIGSEGV.\n");
	}

	fprintf(stderr, "Capturing ...\n");

	if (pcap_loop(pcap_handle, -1, &my_pcap_handler, NULL) == -1) {
		fprintf(stderr, "[FATAL] pcap_loop failed. %s\n",
			pcap_geterr(pcap_handle));
	}

	if (!(opt_flags & OPT_TRACE)) {
		if (pcap_stats(pcap_handle, &ps) == -1) {
			fprintf(stderr, "pcap_stats failed. %s\n", pcap_geterr(pcap_handle));
		} else {
			fprintf(stderr, "%u packets received\n", ps.ps_recv);
			fprintf(stderr, "%u packets dropped\n", ps.ps_drop + ps.ps_ifdrop);
		}
	}

	pcap_close(pcap_handle);

	http_cleanup();

	if (pcap_dumper_handle) {
		pcap_dump_close(pcap_dumper_handle);

		fprintf(stderr, "Written %s\n", dump_fname);
		if (!(opt_flags & OPT_DUMP)) {
			free(dump_fname);
		}
	}

...
}

If you need the complete source code, please add the WeChat number (c17865354792)

curl触发SNI字段解析

curl -k --insecure "https://www.baidu.com"
curl www.baidu.com

该程序从TLS握手客户端Hello消息中提取服务器名称指示(SNI)字段(RFC 4366),并从HTTP/1.1请求中提取主机请求头字段(RFC 2616)。

它接受一个网络接口作为输入来监控其流量(可选地以混杂模式),或者接受一个PCAP文件作为输入来读取。默认情况下,它使用BPF以目标端口为80或443的TCP数据包为目标,但如果相应配置,它将处理其他端口上的TLS和HTTP数据包以及UDP数据包。捕获的流量可以保存到PCAP文件中。

总结

SNI,即服务器名称指示,是TLS协议的扩展。它允许在握手过程开始时通过客户端告诉服务器正在连接的主机名称,从而解决一个服务器拥有多个域名的情况。

在TLS握手信息中并没有携带客户端要访问的目标地址,导致当一台服务器有多个虚拟主机,且每个主机的域名不一样,使用了不一样的证书时,不知道和哪台虚拟主机进行通信。而SNI允许Web服务器通过SSL或TLS握手的扩展在单个IP地址上托管多个站点,从而使得HTTPS网站具有唯一的TLS证书,即使它们位于共享IP地址上。使用SNI时,服务器的主机名包含在TLS握手中,这使得HTTPS网站具有唯一的TLS证书,即使它们位于共享IP地址上也是如此。

Welcome to follow WeChat official account【程序猿编码

参考:RFC 4366、RFC 2616

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

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

相关文章

Spring中动态代理设计模式

目录 一、什么是动态代理 二、动态代理开发步骤 2.1 搭建开发环境 2.2 具体过程 三、动态字节码技术 四、动态代理开发简化代理开发 一、什么是动态代理 其实不管是静态代理还是动态代理其本质都是一样的,都是通过代理类为目标类增加额外功能,从而方便目…

Day7力扣打卡

打卡记录 合法分组的最少组数(贪心) 链接 举例说明,假设 c n t [ x ] 32 cnt[x]32 cnt[x]32, k 10 k10 k10,那么 32 10 10 10 2 321010102 321010102,多出的 2 2 2 可以分成两个 1 1 1&#xf…

Verilog基础:$fopen和$fclose系统函数、任务的使用

相关阅读 Verilog基础https://blog.csdn.net/weixin_45791458/category_12263729.html?spm1001.2014.3001.5482 $fopen和$fclose是两个用于打开和关闭文件的系统函数、任务。最初,在Verilog-1995标准中,最多只能同时打开32个文件,其所使用的…

Flink部署模式及核心概念

一.部署模式 1.1会话模式(Session Mode) 需要先启动一个 Flink 集群,保持一个会话,所有提交的作业都会运行在此集群上,且启动时所需的资源以确定,无法更改,所以所有已提交的作业都会竞争集群中…

2023/10/22总结

项目上 登录注册忘记密码已经全部完善——连接数据库,发送验证码等 把ER图和项目功能点也给做完了(可能后期还需要修改 ,因为问题会在实践的时候出现) 功能点图 刷题记录 接下来的任务是争取早日完成这个项目。

图论04-【无权无向】-图的广度优先遍历BFS

文章目录 1. 代码仓库2. 广度优先遍历图解3.主要代码4. 完整代码 1. 代码仓库 https://github.com/Chufeng-Jiang/Graph-Theory 2. 广度优先遍历图解 3.主要代码 原点入队列原点出队列的同时,将与其相邻的顶点全部入队列下一个顶点出队列出队列的同时,将…

Python基础入门例程4-NP4 读入整数数字

描述 在学会读入字符串以后,小白还想要读入整数,请你帮他使用input函数读入数字并输出数字与变量类型。 输入描述: 输入只有整数。 输出描述: 将输入的数字输出,同时换行输出变量类型。 示例1 输入: …

《算法通关村第二关黄金挑战一一K个一组反转》

《算法通关村第二关黄金挑战一一K个一组反转》 描述 每 k 个节点一组进行翻转,请你返回翻转后的链表。k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。 解法 头插法 理解…

【超级基础版】十进制与二进制的转换

目录 一、为什么是二进制? 二、二进制的加法和乘法 三、二进制向十进制转换 四、十进制整数向二进制转换 五、十进制小数向二进制小数的转换 六、八进制和十六进制的引入 一、为什么是二进制? 我们知道电脑的数据本质上是0和1,就是我们…

已更新!c++第四章知识点合集(自定义函数的格式和使用方法详解, #include,函数的嵌套 递归,局部变量与全局变量的区别等等)

c知识点合集已经完成欢迎前往主页查看,点点赞点点关注不迷路哦 点我进入c第一章知识点合集 MYSQL知识点持续更新中 MYSQL第一章节DDL数据定义语言的操作----点我进入 MYSQL第二章节DDL-数据库操作语言 DQL-数据查询语言----点我进入 MYSQL-第三章节DCL-管理用户&…

库函数qsort的使用

在排序时,我们通常写的函数只能排一种固定的类型,那有没有一种方法可以用来对所有的数据类型,进行排序呢?库函数中的qsort函数就可以实现这种排序。 首先qsort的函数参数有四个,第一个是数组的起始地址(即数组名)&…

【ML】cheatsheet

LR 原理与面试题目DT, Adaboost, GBDT, xgboost 原理 细节 与 例子 https://www.cnblogs.com/createMoMo/p/12635709.html xgboost挺详细的算法原理与例子 https://zhuanlan.zhihu.com/p/660468945 着重lightgbm就xgboost的改善方向 https://zhuanlan.zhihu.com/p/366952043机器…

什么是卷积神经网络?解决了什么问题?

什么是卷积神经网络? 卷积神经网络(Convolutional Neural Network,CNN)是一种深度神经网络模型,主要用于图像识别、语音识别和自然语言处理等任务。它通过卷积层、池化层和全连接层来实现特征提取和分类。 解决了什么问…

pycharm使用Git拉取最新代码(配置了远程服务器)

首先分享一下如何在pycharm设置代理(毕竟pull代码往往是从GitHub上)。因为即便本地开启了代理,PyCharm并不会自动使用它。需要在PyCharm的设置中手动配置代理。 下面是在PyCharm中设置代理的步骤: 主菜单中选择File > Settin…

Node学习笔记之MySQL基本使用

使用 SQL 管理数据库 其实写接口简单来说就是操作数据库数据,所以我们需要学会数据库的增、删、查、改等基本操作 1. 什么是 SQL SQL(英文全称:Structured Query Language)是结构化查询语言,专门用来访问和处理数据…

Kubernetes技术与架构-网络 1

基于OSI网络模型,Kubernetes集群的网络策略包括7层负载均衡的网关路由策略,以及4层3层的网络IP地址策略,这些网络策略是保证Kubernetes集群内Pod之间的网络访问的互联互通,本文主要描述Kubernetes集群的网络策略的基本使用方式。 …

基于nodejs+vue旅行社网站系统

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性:…

Linux笔记之diff和vimdiff

Linux笔记之diff和vimdiff code review! 文章目录 Linux笔记之diff和vimdiff一.diff1.1.使用diff比较文件夹1.2.使用diff比较文件1.4.colordiff——带颜色输出差异 二.vimdiff2.1.vimdiff颜色差异2.2.vimfiff调整栏宽2.3.修改颜色变谈,使代码可以看清楚2.4.vimdif…

[AUTOSAR][诊断管理][ECU][$14] 清除诊断相关信息

文章目录 一、简介(1)应用场景(2)清除DTC原理(3) 请求格式二、示例代码(1) 14_cls_dtc_info.c三、 常见bug大揭秘一、简介 根据ISO14119-1标准中所述,诊断服务14主要用于Client向Server(ECU)请求清除诊断相关信息。 (1)应用场景 一般而言,14诊断服务,主要应用场景…

面向对象(基础)特征一:封装性(encapsulation)

文章目录 一、介绍(1)封装性(2)权限修饰符 二、案例(1)案例1 三、练习(1)练习1(2)练习2(3)练习3(4)练习4 面向…