SNI

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.