如何在url中使用IP时分配SNI?
我有一台服务器1.1.1.1为多个主机A.com和B.com服务。 所以每个域都有自己的证书。如何在url中使用IP时分配SNI?
当我使用基于主机的网址:
URL url = new URL("https://B.com/23o8PS");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.connect();
Certificate[] certificates = ((HttpsURLConnection) conn).getServerCertificates();
System.out.println(certificates.length + " " + certificates[0].toString());
我能顺利拿到B.com的证书。但是,如果使用基于IP地址(通过添加Host字段,表示主机),如下:
URL url = new URL("https://1.1.1.1/23o8PS");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Host", "B.com");
conn.connect();
Certificate[] certificates = ((HttpsURLConnection) conn).getServerCertificates();
System.out.println(certificates[0].toString());
我刚刚A.com的证书,就好像它是返回来自服务器的默认证书。看起来像SNI不能根据HttpURLConnection的HOST HEADER字段来设置。
任何建议如何处理这种情况?
Server Name Identification (SNI)是TLS握手的一部分。它在一开始就被传递并用于确定使用哪个服务器证书。该握手发生在任何HTTP请求发送之前 - 如果没有,HTTP请求将以未加密方式发送。
每RFC 4366:
3.1。服务器名称指示
TLS不提供机制让客户端告诉服务器它正在联系的服务器的名称。客户可能希望提供这些信息,以便于在单个底层网络地址上连接到托管多个“虚拟”服务器的服务器。
...
目前,唯一支持的服务器名称是DNS主机名;但是,这并不意味着TLS对DNS有任何依赖性,并且将来可能会添加其他名称类型(由更新此文档的RFC)。 TLS可以将提供的服务器名称视为不透明数据,并将名称和类型传递给应用程序。
“HostName”包含服务器的完全限定DNS主机名,正如客户所理解的那样。主机名表示为使用UTF-8编码[UTF8]的字节字符串,没有尾部点。
...
文字IPv4和IPv6地址在 “主机名” 是不允许的。
您不能在SNI中使用IP地址。
这很有道理 - SNI的全部目的是在一个IP地址上支持多个虚拟主机。
我想是为请求而不是DNS指定IP。
所以我制定了另一种方法来解决这个问题。
我定制一个SSLSocketFactory的,推翻的createSocket方法与SSLSocket的指定的IP地址。这也适用于具有多个域的单个IP地址场景。
使用URL中的主机名。 AFAIK,整个SNI协商发生在任何HTTP标头传输之前。 – CommonsWare
@CommonsWare我知道它发生在标题传输之前,但SNI必须在它与服务器协商之前获取主机信息,我认为它来自url。所以我想知道它是否可以从标题字段而不是网址获得主机,因为标题字段具有更高的优先级。而这似乎不起作用。因此,我无法通过基于IP的请求来做到这一点? – Ryan
@Ryan:我怀疑你可以在HttpUrlConnection的抽象层面处理这个问题。你可以在套接字级别上完成,参见http://developer.android.com/reference/android/net/SSLCertificateSocketFactory.html#setHostname(java.net.Socket,java.lang.String)。 –