证书链验证通过只说明“这是某个受信任 CA 签发的证书”,并不说明你连上的就是你想连的主机——后者要靠主机名验证。这篇用 15 种证书与主机名的组合、四种 Java 客户端量出:哪里会检查、匹配规则是什么、以及怎样的写法让它悄悄失效。
四种客户端:①SSLSocket 默认设置;②SSLSocket + setEndpointIdentificationAlgorithm("HTTPS");③HttpsURLConnection;④java.net.http.HttpClient
[A SAN=DNS:localhost]
连接 localhost:①握手成功;②握手成功
③HTTP 200;④HTTP 200
连接 LOCALHOST:①握手成功;②握手成功
③HTTP 200;④HTTP 200
连接 localhost.:①握手成功;②握手成功
③HTTP 200;④失败 IllegalArgumentException:Server name value of host_name cannot have the trailing dot
[B SAN=DNS:other.test(证书是给另一个域名的)]
连接 right.test:①握手成功;②失败 CertificateException:No subject alternative DNS name matching right.test found.
③失败 CertificateException:No subject alternative DNS name matching right.test found.;④失败 CertificateException:No subject alternative DNS name matching right.test found.
[C 只有 CN=cn.test,没有 SAN]
连接 cn.test:①握手成功;②握手成功
③HTTP 200;④HTTP 200
[D SAN=DNS:*.wild.test]
连接 a.wild.test:①握手成功;②握手成功
③HTTP 200;④HTTP 200
连接 wild.test:①握手成功;②失败 CertificateException:No subject alternative DNS name matching wild.test found.
③失败 CertificateException:No subject alternative DNS name matching wild.test found.;④失败 CertificateException:No subject alternative DNS name matching wild.test found.
连接 a.b.wild.test:①握手成功;②失败 CertificateException:No subject alternative DNS name matching a.b.wild.test found.
③失败 CertificateException:No subject alternative DNS name matching a.b.wild.test found.;④失败 CertificateException:No subject alternative DNS name matching a.b.wild.test found.
[E SAN=IP:127.0.0.1]
连接 127.0.0.1:①握手成功;②握手成功
③HTTP 200;④HTTP 200
连接 localhost:①握手成功;②失败 CertificateException:No name matching localhost found
③失败 CertificateException:No name matching localhost found;④失败 CertificateException:No name matching localhost found
[F SAN=DNS:127.0.0.1(把 IP 写成 DNS 名)]
连接 127.0.0.1:①握手成功;②失败 CertificateException:No subject alternative names matching IP address 127.0.0.1 found
③失败 CertificateException:No subject alternative names matching IP address 127.0.0.1 found;④失败 CertificateException:No subject alternative names matching IP address 127.0.0.1 found
[G SAN=DNS:w*.part.test(部分通配)]
连接 w1.part.test:①握手成功;②握手成功
③HTTP 200;④HTTP 200
[H SAN=DNS:UPPER.TEST(大写)]
连接 upper.test:①握手成功;②握手成功
③HTTP 200;④HTTP 200
[I SAN=DNS:dot.test + CN=wrong.test(SAN 与 CN 不一致)]
连接 dot.test:①握手成功;②握手成功
③HTTP 200;④HTTP 200
连接 wrong.test:①握手成功;②失败 CertificateException:No subject alternative DNS name matching wrong.test found.
③失败 CertificateException:No subject alternative DNS name matching wrong.test found.;④失败 CertificateException:No subject alternative DNS name matching wrong.test found.
汇总:共 15 种(证书, 主机名)组合;①默认 SSLSocket 成功但②加上端点标识后失败的有 6 种;①成功的共 15 种,②成功的共 9 种,③成功的共 9 种,④成功的共 8 种大小写:SAN=localhost,连接 LOCALHOST → 成功 没有 SAN 时回退到 CN:证书只有 CN=cn.test,连接 cn.test → 成功 有 SAN 时 CN 被忽略:SAN=dot.test、CN=wrong.test,连接 dot.test → 成功;连接 wrong.test → 失败(CertificateException:No subject alternative DNS name matchin) 通配符 *.wild.test:连接 a.wild.test → 成功;连接 wild.test → 失败(CertificateException:No subject alternative DNS name matchin);连接 a.b.wild.test → 失败(CertificateException:No subject alternative DNS name matchin) 部分通配 w*.part.test:连接 w1.part.test → 成功 IP 地址:SAN=IP:127.0.0.1,连接 127.0.0.1 → 成功;连接 localhost → 失败(CertificateException:No name matching localhost found);SAN=DNS:127.0.0.1,连接 127.0.0.1 → 失败(CertificateException:No subject alternative names matching I) 主机名带结尾的点(localhost.):①成功;②成功;③成功;④失败(IllegalArgumentException:Server name value of host_name cann)
[默认] HttpsURLConnection + setHostnameVerifier((h, s) -> true):HTTP 200;HttpsURLConnection + 返回 false 的验证器:失败 IOException:HTTPS hostname wrong: should be <right.test>;(自定义 HostnameVerifier 被调用:right.test);HttpClient:失败 CertificateException:No subject alternative DNS name matching right.test found.
SSLContext 的默认 SSLParameters.endpointIdentificationAlgorithm = null
[-Djdk.internal.httpclient.disableHostnameVerification=true] HttpsURLConnection + setHostnameVerifier((h, s) -> true):HTTP 200;HttpsURLConnection + 返回 false 的验证器:失败 IOException:HTTPS hostname wrong: should be <right.test>;(自定义 HostnameVerifier 被调用:right.test);HttpClient:HTTP 200
[-Dcom.sun.net.ssl.checkRevocation=false(与主机名无关,对照)] HttpsURLConnection + setHostnameVerifier((h, s) -> true):HTTP 200;HttpsURLConnection + 返回 false 的验证器:失败 IOException:HTTPS hostname wrong: should be <right.test>;(自定义 HostnameVerifier 被调用:right.test);HttpClient:失败 CertificateException:No subject alternative DNS name matching right.test found.