SNI 介绍
SNI (Server Name Indication) 是用来改善服务器与客户端 SSL (Secure Socket Layer) 和 TLS (Transport Layer Security) 的一个扩展。
主要解决一台服务器只能使用一个证书(一个域名)的缺点,随着服务器对虚拟主机的支持,一个服务器上可以为多个域名提供服务,因此SNI必须得到支持才能满足需求。
SNI 的工作原理
它的工作原理是:在客户端与服务器建立 SSL 连接之前,Client Hello 阶段会通过 SNI 扩展,将要访问站点的域名(Hostname)提前告诉服务器,这样服务器会根据这个域名匹配对应的证书返回给客户端,以完成校验过程。
目前,大多数操作系统和浏览器都已经很好地支持SNI扩展。
SNI兼容性
说明: SNI兼容TLS1.0及以上协议,但不被SSL支持,并且SNI扩展是双向的,必须客户端、服务端同时支持发送与接收,该特性才算生效。
SNI支持以下桌面版浏览器:
- Chrome 5及以上版本
- Chrome 6及以上版本(Windows XP)
- Firefox 2及以上版本
- IE 7及以上版本(运行在Windows Vista/Server 2008及以上版本系统中,在XP系统中任何版本的IE浏览器都不支持SNI)
- Konqueror 4.7及以上版本
- Opera 8及以上版本
- Safari 3.0 on Windows Vista/Server 2008及以上版本,Mac OS X 10.5.6 及以上版本
SNI支持以下手机端浏览器:
- Android Browser on 3.0 Honeycomb及以上版本
- iOS Safari on iOS 4及以上版本
- Windows Phone 7及以上版本
SNI支持以下服务器:
- Apache 2.2.12及以上版本
- Apache Traffic Server 3.2.0及以上版本
- HAProxy 1.5及以上版本
- IIS 8.0及以上版本
- lighttpd 1.4.24及以上版本
- LiteSpeed 4.1及以上版本
- nginx 0.5.32及以上版本
SNI 支持以下命令行:
- cURL 7.18.1及以上版本
- wget 1.14及以上版本
Nginx 配置 SNI
检测支持性
nginx -V
...
TLS SNI support enabled
...
核心配置
全站SSL证书配置均需要使用变量形式匹配赋予。
- 启用了 sni 的 nginx ,如下变量会被赋值
$ssl_server_name
- 证书命令 ssl_certificate ,也可以通过变量进行设置,形如:
ssl_certificate $ssl_server_name.crt;
ssl_certificate_key $ssl_server_name.key;
测试试验
多站点 SSL 配置
传统配置,拆分 server ,单独写证书配置
server { listen 80; listen 443 ssl; server_name test1.nestealin.com; charset utf-8; access_log logs/test1.access.main.log main; error_log logs/test1.error.log error; ssl_certificate /data/keys/server1.cer; ssl_certificate_key /data/keys/server1.key; add_header content-type application/json; return 200 'ok'; } server { listen 80; listen 443 ssl; server_name test2.nestealin.com; charset utf-8; access_log logs/test2.access.main.log main; error_log logs/test2.error.log error; ssl_certificate /data/keys/server2.cer; ssl_certificate_key /data/keys/server2.key; add_header content-type application/json; return 200 'ok'; }
MAP 方式正则匹配证书名称
map $ssl_server_name $sni_test1_string { "~*^.*\.nestealin\.com.*" 'server'; default ''; } server { listen 80; listen 443 ssl; server_name _; charset utf-8; access_log logs/test1.access.main.log main; error_log logs/test1.error.log error; ssl_certificate /data/keys/$sni_test1_string.cer; ssl_certificate_key /data/keys/$sni_test1_string.key; add_header content-type application/json; return 200 'ok'; }
本次我们采用 MAP 方式进行通配测试
访问测试
写hosts
192.168.7.55 test1.nestealin.com 192.168.7.55 test2.nestealin.com 192.168.7.55 test3.baidu.com
通过 openssl s_client 方式请求验证
openssl s_client -connect 192.168.7.55:443 -tls1_2 -servername test1.nestealin.com | grep subject
test1.nestealin.com 匹配到根域域名证书文件 server.cer、server.key,验证通过
depth=3 O = Digital Signature Trust Co., CN = DST Root CA X3 verify return:1 depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1 verify return:1 depth=1 C = US, O = Let's Encrypt, CN = R3 verify return:1 depth=0 CN = *.nestealin.com verify return:1 subject=/CN=*.nestealin.com
同样 test2.nestealin.com 属于同一根域,验证通过
[root@localhost ~]# openssl s_client -connect 192.168.7.55:443 -tls1_2 -servername test2.nestealin.com | grep subject depth=3 O = Digital Signature Trust Co., CN = DST Root CA X3 verify return:1 depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1 verify return:1 depth=1 C = US, O = Let's Encrypt, CN = R3 verify return:1 depth=0 CN = *.nestealin.com verify return:1 subject=/CN=*.nestealin.com
test3.baidu.com 由于匹配不到证书,握手失败
[root@localhost ~]# openssl s_client -connect 192.168.7.55:443 -tls1_2 -servername test3.baidu.com | grep subject 140177640814480:error:14094438:SSL routines:ssl3_read_bytes:tlsv1 alert internal error:s3_pkt.c:1493:SSL alert number 80 140177640814480:error:1409E0E5:SSL routines:ssl3_write_bytes:ssl handshake failure:s3_pkt.c:659:
验证日志文件报错信息,匹配证书名为空,找不到证书导致握手失败
2021/08/26 01:32:26 [error] 8056#0: *45 cannot load certificate "/data/keys/.cer": BIO_new_file() failed (SSL: error:02001002:system library:fopen:No such file or directory:fopen('/data/keys/.cer','r') error:2006D080:BIO routines:BIO_new_file:no such file) while SSL handshaking, client: 192.168.7.27, server: 0.0.0.0:443
不带域名直接请求主机443端口,同样因为匹配不到证书,导致握手失败
[root@localhost ~]# openssl s_client -connect 192.168.7.55:443 -tls1_2 CONNECTED(00000003) 139661997975440:error:14094438:SSL routines:ssl3_read_bytes:tlsv1 alert internal error:s3_pkt.c:1493:SSL alert number 80 139661997975440:error:1409E0E5:SSL routines:ssl3_write_bytes:ssl handshake failure:s3_pkt.c:659: --- no peer certificate available --- No client certificate CA names sent
以上结果均符合预期,实现单机支持多域名HTTPS证书匹配。
- 如果主机符合正则匹配,则匹配已有证书,通过 SSL 握手;
- 如果无法匹配主机MAP,则无法找到证书配置,导致握手失败;
总结
SNI 不是 Nginx 的功能,无需单独配置,它的特性仅是为了让单IP主机支持多域名HTTPS配置、不同域名使用不同证书。
- 相关链接
- nginx使用SNI功能的方法
- SNI兼容性导致HTTPS访问异常(服务器证书不可信)
- WireShark抓包验证SNI