【问题标题】:HostNameVerifier is not called for sites with wildcard or SAN certificate具有通配符或 SAN 证书的站点不调用 HostNameVerifier
【发布时间】:2018-10-05 12:29:36
【问题描述】:

在我们的应用程序中,我们通过 HTTPS 对身份验证服务进行 HTTP 调用。 我需要确保我们进行所有必要的检查以确保连接安全。作为其中的一部分,我必须实现以下内容:

  1. 验证身份验证服务器的证书是否由知名 CA 颁发。
  2. 验证身份验证服务器证书中存在的主机名是否与我们用于连接身份验证服务器的主机名匹配。

我对第 1 项没有任何问题。

对于第 2 项,我正在尝试使用 HostNameVerifier 实现。

到目前为止我的代码:

package com.company;

import sun.security.x509.X500Name;

import javax.net.ssl.*;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

public class Main {

    public static void main(String[] args) throws Exception {

        URL endpoint = new URL("https://example.com");
        HttpsURLConnection conn = (HttpsURLConnection) endpoint.openConnection();
        conn.setHostnameVerifier(new MyHostNameVerifier());

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, new TrustManager[] {trustAll}, null);
        conn.setSSLSocketFactory(sslContext.getSocketFactory());

        System.out.println("HTTP Response: " + conn.getResponseCode());
    }

    public static final X509TrustManager trustAll = new X509TrustManager() {

        @Override
        public void checkClientTrusted(X509Certificate[] x509Certificates,
                                       String s) throws CertificateException {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] x509Certificates,
                                       String s) throws CertificateException {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    };

    public static class MyHostNameVerifier implements HostnameVerifier {
        @Override
        public boolean verify(String hostName, SSLSession sslSession) {
            System.out.println("*** Validating host name for " + hostName);
            return false;
        }
    }
}

我的期望是 MyHostNameVerifier#verify 将始终被调用,并且我可以在那里进行某些检查。

但是,我发现 HostNameVerifier#verify 方法仅在我使用站点的 IP 地址时调用,或者仅当主机名与证书的主题或任何主题备用名称不匹配时调用。

HostNameVerifier#verify 在下列情况下不会被调用:

  1. 如果站点的证书是通配符证书,并且它与使用的主机名匹配。
  2. 如果站点的证书是 SAN 证书,并且我使用的主机名是使用者的 CN 或列为使用者备用名称值之一。

例如: 在上面的代码中,我试图连接到“example.com”。
“example.com”的SSL证书颁发给CN“www.example.org”。
但是,“example.com”列在证书的“主题备用名称”(SAN) 中。

如果我改为使用“example.com”的 IP 地址,则会调用我的主机名验证程序。

URL endpoint = new URL("https://93.184.216.34/");

谁能向我确认 Java 在内部处理通配符证书和 SAN 证书检查?如果是,那么,我可能不需要做任何额外的代码更改。

【问题讨论】:

    标签: java security ssl-certificate


    【解决方案1】:

    引用Java documentation 的话,我可能会有点超出我的界限,但它是我们最纯粹的来源。

    在握手过程中,如果 URL 的主机名和服务器的标识主机名不匹配,验证机制可以回调该接口的实现者以确定是否应该允许此连接。

    当 URL 主机名验证的默认规则失败时使用这些回调。

    这基本上证实了您的观察,即一旦您输入了无效证书,它就会并且只有在那时才调用该函数以查看您是否仍要允许它。通过您自己的测试,正确的通配符证书没有调用该函数,我们可以说他明确确认它有效并且不需要您的帮助。 SAN证书也一样。

    【讨论】:

    • 我回家后要看看内部处理,您能告诉我们您使用的是哪个 Java 版本吗?
    • 我正在使用 Java 8。感谢您对此进行调查。
    猜你喜欢
    • 2012-12-25
    • 1970-01-01
    • 2014-02-24
    • 2018-07-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多