【问题标题】:android retrofit Hostname not verifiedandroid改造主机名未验证
【发布时间】:2017-01-09 06:50:24
【问题描述】:

我获得了为 IP 地址(不是通用名称)颁发的证书,我正在尝试使用该证书连接到服务器。

OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
OkHttpClient okHttpClient = builder.build();
Gson gson = new GsonBuilder()
                .setLenient()
                .create();
retrofit = new Retrofit.Builder()
                .baseUrl(url)
                .client(okHttpClient)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build();
ServerRouts service = retrofit.create(ServerRouts.class);
Resp_json> call = service.login(param, user, pw);

我得到一个错误:

Hostname 11.8.222.333 not verified:

但是当我使用时

builder.hostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });

然后一切正常。

如何在不关闭主机名验证程序的情况下解决该错误?

附:我为 IP (11.8.222.333) 颁发的证书

【问题讨论】:

  • 如果它工作正常,请使用休息客户端检查一次,而不是检查改造问题!

标签: android ssl-certificate retrofit2


【解决方案1】:

我重新定义了这样的验证方法(只是从 DefaultHostnameVerifier.java 复制了源代码),现在一切正常。我不知道为什么它不起作用,但现在很好。

builder.hostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {

                Certificate[] certs;
                try {
                    certs = session.getPeerCertificates();
                } catch (SSLException e) {
                    return false;
                }
                X509Certificate x509 = (X509Certificate) certs[0];
                // We can be case-insensitive when comparing the host we used to
                // establish the socket to the hostname in the certificate.
                String hostName = hostname.trim().toLowerCase(Locale.ENGLISH);
                // Verify the first CN provided. Other CNs are ignored. Firefox, wget,
                // curl, and Sun Java work this way.
                String firstCn = getFirstCn(x509);
                if (matches(hostName, firstCn)) {
                    return true;
                }
                for (String cn : getDNSSubjectAlts(x509)) {
                    if (matches(hostName, cn)) {
                        return true;
                    }
                }
                return false;

            }
        });


private String getFirstCn(X509Certificate cert) {
        String subjectPrincipal = cert.getSubjectX500Principal().toString();
        for (String token : subjectPrincipal.split(",")) {
            int x = token.indexOf("CN=");
            if (x >= 0) {
                return token.substring(x + 3);
            }
        }
        return null;
    }

【讨论】:

  • 这里的getFirstCn(x509)方法是什么
  • 我从这段代码中遇到了崩溃:AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher Process: com.igor.mobile, PID: 30362 java.lang.NullPointerException: Attempt to get length of null array
【解决方案2】:

OkHttpClient.Builder 的默认 HostNameVerifierokhttp3.internal.tls.OkHostnameVerifier。看来此验证程序不会将主机名与对等证书中的公用名匹配,而仅与主题替代名称匹配。此行为是设计使然,而不是错误 - 请查看此问题:https://github.com/square/okhttp/issues/4966

两个选项解决它:

  1. 如果您想继续使用OkHostnameVerifier,您应该确保您的服务器证书包含主题备用名称。至少重复 SAN 中的通用名称。我发现这个要点有助于这样做:https://gist.github.com/croxton/ebfb5f3ac143cd86542788f972434c96

  2. 如果您无法控制服务器证书,您可以使用与证书的 CN 匹配的替代 HostNameVerifier 实现。 @Rainmaker 的答案 (https://stackoverflow.com/a/41543384/978164) 中的那个可以。我还在 Apache 的 httpclient 包中发现 org.apache.http.conn.ssl.DefaultHostnameVerifier 可以正常工作,而且它可能是一个更彻底的实现。

// instantiate and configure your OkHttpClient.Builder and then:
builder.hostnameVerifier(new org.apache.http.conn.ssl.DefaultHostnameVerifier());

【讨论】:

    【解决方案3】:

    事实上,这是错误的答案,它只是一个短期修复,以避免出现错误。 如果您不完全需要 HTTPS,我建议使用以下解决方案:

    1. 使用 HTTP 而不是 HTTPS

    2. 将 URL 添加到 network_security_config.xml 文件中,并将其放在 res/xml/ 目录中

    3. 在清单文件中将以下属性添加到应用程序标签

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-08-25
      • 2020-11-15
      • 2013-01-15
      • 1970-01-01
      • 1970-01-01
      • 2015-11-30
      • 2019-01-20
      相关资源
      最近更新 更多