SNI是TLS协议扩展, 在握手的开始标识其尝试连接的主机名, 当多个HTTPS服务部署在同一IP地址上,客户端就可以通过这个标识指定它将使用哪一个服务, 同时服务端也无需使用相同的证书,它在概念上相当于HTTP/1.1基于名称的虚拟主机。SNI扩展最早在2003年的RFC 3546中出现。
HTTP服务通过 Http header “Host”, 来选择指定服务, HTTPS服务就是通过这个SNI来区分。通过可以通过nginx来验证一下
#user nobody; worker_processes 1; #error_log logs/error.log; error_log logs/error.log debug; #error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 1024; } http { access_log logs/access.log ; server { listen 8443 ssl ; server_name test; ssl_certificate certs/cert.crt; ssl_certificate_key certs/cert.key; location / { return 200 "https-test\r\n"; } } server { listen 8443 ssl ; server_name garlic; ssl_certificate certs/cert2.crt; ssl_certificate_key certs/cert.key; location / { return 200 "https-garlic\r\n"; } } server { listen 8443 ssl ; server_name 10.10.10.10; ssl_certificate certs/cert3.crt; ssl_certificate_key certs/cert.key; location / { root html; index index.html index.htm; } } server { listen 9443 ssl ; server_name snitest; ssl_certificate certs/cert4.crt; ssl_certificate_key certs/cert.key; location / { root html; index index.html index.htm; } } server { listen 8080 ; server_name http-test; location / { return 200 "http-test\r\n"; } } server { listen 8080 ; server_name http-garlic; location / { return 200 "http-garlic\r\n"; } } }
http使用curl 通过后-H 增加header可以选择不同的主机
[garlic@dev nginx-quic]$ curl -H "Host: http-test" http://127.0.0.1:8080/ http-test [garlic@dev nginx-quic]$ curl -H "Host: http-garlic" http://127.0.0.1:8080/ http-garlic
https服务可以使用openssl sclient来验证
[garlic@dev nginx-quic]$ openssl s_client -connect 127.0.0.1:8443 -servername test CONNECTED(00000003) depth=0 C = CN, ST = BEIJING, L = beijing, OU = organizationalUnitName, CN = test verify error:num=18:self-signed certificate verify return:1 depth=0 C = CN, ST = BEIJING, L = beijing, OU = organizationalUnitName, CN = test verify return:1 --- Certificate chain 0 s:C = CN, ST = BEIJING, L = beijing, OU = organizationalUnitName, CN = test i:C = CN, ST = BEIJING, L = beijing, OU = organizationalUnitName, CN = test 。。。。 [garlic@dev nginx-quic]$ openssl s_client -connect 127.0.0.1:8443 -servername garlic CONNECTED(00000003) depth=0 C = CN, ST = SHENZHEN, L = ShenZhen, OU = organizationalUnitName, CN = garlic verify error:num=18:self-signed certificate verify return:1 depth=0 C = CN, ST = SHENZHEN, L = ShenZhen, OU = organizationalUnitName, CN = garlic verify return:1 --- Certificate chain 0 s:C = CN, ST = SHENZHEN, L = ShenZhen, OU = organizationalUnitName, CN = garlic i:C = CN, ST = SHENZHEN, L = ShenZhen, OU = organizationalUnitName, CN = garlic [garlic@dev nginx-quic]$ openssl s_client -connect 127.0.0.1:8443 -servername 10.10.10.10 CONNECTED(00000003) depth=0 C = CN, ST = CN, L = BEIJING, OU = TEST, CN = 10.10.10.10 verify error:num=18:self-signed certificate verify return:1 depth=0 C = CN, ST = CN, L = BEIJING, OU = TEST, CN = 10.10.10.10 verify return:1 --- Certificate chain 0 s:C = CN, ST = CN, L = BEIJING, OU = TEST, CN = 10.10.10.10 i:C = CN, ST = CN, L = BEIJING, OU = TEST, CN = 10.10.10.10
可以看到servername传送的不同, 选择的ssl服务也不同。特殊的如果servername 是一个ip, 处理也是一样的,创
这里servername与证书中的common name设置为一样,如果要支持多个域名或ip可以通过SAN配置。
对应nginx中sni的判断 ngx_http_find_virtual_server
如果是https服务 被调用两次 针对servername的回调函数, ngx_http_ssl_servername, 第二次调用是ngx_http_set_virtual_server。
所以可以设置SNI来选择证书, 然后通过后Header来重新选择服务。
curl -ksv --resolve garlic:8443:127.0.0.1 https://garlic:8443 -H "Host: test" 。。。。 * Server certificate: * subject: C=CN; ST=SHENZHEN; L=ShenZhen; OU=organizationalUnitName; CN=garlic * start date: Dec 9 01:45:35 2023 GMT * expire date: Dec 6 01:45:35 2033 GMT * issuer: C=CN; ST=SHENZHEN; L=ShenZhen; OU=organizationalUnitName; CN=garlic 。。。。 https-test
在wiki SNI此词条里还讲到到安全部分, SNI是明文存放的, 所以虽然tls是加密的,其实还是查询出来访问的网站的名称。Encrypted Client Hello (ECH)则是为了解决这个问题。
参考及引用
https://jvns.ca/blog/2016/07/14/whats-sni/
https://en.wikipedia.org/wiki/Server_Name_Indication#Security_implications
图片from李華欽
Comments are closed.