【问题标题】:java.io.IOException: Hostname was not verifiedjava.io.IOException:未验证主机名
【发布时间】:2013-01-15 04:48:11
【问题描述】:

我正在尝试从 Andorid 4.1.1 版中的 Android 应用程序连接到 URL,并且我收到问题标题中指示的错误,但是当我尝试从 Andorid 4.0 版连接相同的 URL 时。 4 或 3.1,一切正常。

代码片段:

    try {
        .
        .
        .
        URL url = new URL(urlStr);
        Log.i(TAG,"[ URL ] " + urlStr);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        int size = conn.getContentLength();
        int responsecode = conn.getResponseCode();
        Log.d(TAG, "Responsecode: " + responsecode);
        .
        .
        .
        } catch (Exception e) {
        e.printStackTrace();
        }


private static void trustAllHosts() {

        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                    return new java.security.cert.X509Certificate[] {};
            }

            public void checkClientTrusted(X509Certificate[] chain,
                            String authType) throws CertificateException {
            }

            public void checkServerTrusted(X509Certificate[] chain,
                            String authType) throws CertificateException {
            }
        } };

        try {
                SSLContext sc = SSLContext.getInstance("TLS");
                sc.init(null, trustAllCerts, new java.security.SecureRandom());
                HttpsURLConnection
                                .setDefaultSSLSocketFactory(sc.getSocketFactory());
        } catch (Exception e) {
                System.out.println("IOException : HTTPSRequest::trustAllHosts");
                e.printStackTrace();
        }
    }

但在这里我清楚一件事是“也许证书是自签名证书并且没有将它们包含在 KeyStore 中。

我不明白为什么这个异常只发生在 Android Verison 4.1.1 操作系统中 谢谢。

全栈跟踪

01-31 10:26:08.348: W/System.err(3158): java.io.IOException: Hostname <URL> was not verified
01-31 10:26:08.348: W/System.err(3158):     at libcore.net.http.HttpConnection.verifySecureSocketHostname(HttpConnection.java:223)
01-31 10:26:08.348: W/System.err(3158):     at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:446)
01-31 10:26:08.348: W/System.err(3158):     at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:289)
01-31 10:26:08.348: W/System.err(3158):     at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:239)
01-31 10:26:08.348: W/System.err(3158):     at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:273)
01-31 10:26:08.348: W/System.err(3158):     at libcore.net.http.HttpURLConnectionImpl.getHeaderField(HttpURLConnectionImpl.java:130)
01-31 10:26:08.348: W/System.err(3158):     at java.net.URLConnection.getHeaderFieldInt(URLConnection.java:544)
01-31 10:26:08.348: W/System.err(3158):     at java.net.URLConnection.getContentLength(URLConnection.java:316)
01-31 10:26:08.348: W/System.err(3158):     at libcore.net.http.HttpsURLConnectionImpl.getContentLength(HttpsURLConnectionImpl.java:191)
01-31 10:26:08.348: W/System.err(3158):     at com.ih.util.HelpVideoServices$downloadTask.run(HelpVideoServices.java:172)                                

【问题讨论】:

  • 网址是什么? http 还是 https?域名还是id地址?
  • 您是否有多个具有不同证书的虚拟主机?我目前在 4.1.1 和 4.1.2 中看到了这个问题 - 我认为因为我正在处理的服务器有多个虚拟主机服务于 https 流量,并且可能是因为 Android 和 SNI 的问题 - 另请参阅这个错误 code.google.com/p/android/issues/detail?id=36599跨度>
  • 我前几天遇到了这个问题,
  • 我的应用程序无法在 Android 4.1.1 操作系统中下载某些文件,但在其他 Android 操作系统(3.x、4.0.x、4.1.2)中可以下载相同的代码。我得到了解决方案,在我的应用程序中,一种方法 trustAllHosts() 使用 TrustManager[] 类信任证书。在这个函数中,我添加了 'HttpsURLConnection.setDefaultHostnameVerifier(DO_NOT_VERIFY);' DO_NOT_VERIFY 是 HostnameVerifier 类的对象 final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) { return true; } };

标签: android http


【解决方案1】:

如果您使用没有任何意义的证书运行并且想要绕过它们,您还需要添加一个空主机名验证器以使此代码正常工作

HttpsURLConnection.setDefaultHostnameVerifier(new NullHostNameVerifier());
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new X509TrustManager[]{new NullX509TrustManager()}, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());

以及主机的代码:

import javax.net.ssl.HostnameVerifier ;
import javax.net.ssl.SSLSession;

public class NullHostNameVerifier implements HostnameVerifier {

    @Override   
    public boolean verify(String hostname, SSLSession session) {
        Log.i("RestUtilImpl", "Approving certificate for " + hostname);
        return true;
    }

}

这需要运行一次,但如果您要更改连接对象,则可能需要再次运行它。

【讨论】:

  • 什么是 NullX509TrustManager 它的显示错误。
  • 是的,它有效.. 但是当我在 Playstore 中发布 apk 时拒绝应用程序并显示以下消息“您的应用程序正在使用 HostnameVerifier 接口的不安全实现”。
  • 如何在 OkHttp 上设置这个?
【解决方案2】:

除了@Noam 的回答,这是一个完整的例子:

/**
 * Disables the SSL certificate checking for new instances of {@link HttpsURLConnection} This has been created to
 * aid testing on a local box, not for use on production.
 */
private static void disableSSLCertificateChecking() {
    TrustManager[] trustAllCerts = new TrustManager[] {
        new X509TrustManager() {

            @Override
            public void checkClientTrusted(java.security.cert.X509Certificate[] x509Certificates, String s) throws java.security.cert.CertificateException {
                // not implemented
            }

            @Override
            public void checkServerTrusted(java.security.cert.X509Certificate[] x509Certificates, String s) throws java.security.cert.CertificateException {
                // not implemented
            }

            @Override
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return null;
            }

        }
    };

    try {

        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {

            @Override
            public boolean verify(String s, SSLSession sslSession) {
                return true;
            }

        });
        SSLContext sc = SSLContext.getInstance("TLS");
        sc.init(null, trustAllCerts, new java.security.SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

    } catch (KeyManagementException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
}

希望对你有帮助

【讨论】:

  • 禁用所有 SSL 检查非常糟糕,最好完全剥离 SSL 并使用 HTTP,因为这样几乎没有任何安全性。
  • 有效。感谢 Matias,非常重视使用未签名的开发证书来解决问题,
【解决方案3】:

这可能是因为您在 SSL 上声明的 CN(通用名称)与您发送 HTTP 请求的实际 URL 不匹配。

如果是这样,创建一个新的 SSL 并输入当前的 CN。这应该可以解决问题。

【讨论】:

【解决方案4】:

我在 4.1.1 和 4.1.2 中使用 HTTPSUrlConnection 遇到了这个问题。

经过一番摸索,我发现这是因为我正在处理的 Apache 服务器有多个虚拟主机服务于 https 流量,导致 Android 中出现SNI 问题 - 至少在 JellyBean 之前(我有未经证实的报告说它是在 JB 工作)。

在我的例子中,有 3 个虚拟主机服务于 https 流量:

  • mydomain.com
  • api.mydomain.com(我试图处理的那个)
  • admin.mydomain.com

像这样使用 openssl_client 探测 api.*:

openssl s_client -debug -connect api.mydomain.com:443

...总是返回根域的证书 - 隐藏在输出中的是:

Certificate chain
 0 s:/OU=Domain Control Validated/CN=mydomain.com
 ...

...在 openssl_client 命令行中指定服务器名称:

openssl s_client -debug -servername api.mydomain.com -connect api.mydomain.com:443

...返回了我希望看到的证书:

Certificate chain
 0 s:/OU=Domain Control Validated/CN=api.mydomain.com

我能够通过将根域虚拟主机移动到不同的物理主机来解决问题。

Android HostnameVerifier 似乎可以同时使用多个子域作为虚拟主机,但是将 root 域作为虚拟主机在同一个 apache 中会导致问题。

我不是 sys-admin/dev-ops,因此可能有 Apache 配置选项可以解决我不知道的问题。

【讨论】:

    【解决方案5】:

    请注意 SSL 证书仅适用于域,而不适用于 IP 地址。

    如果你使用IP,请在下面插入代码

    HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier()
            {
                @Override
                public boolean verify(String hostname, SSLSession session)
                {
                    if(hostname.equals("127.0.0.1"))
                         return true;
                }
            });
    

    【讨论】:

    • 请注意,在模拟器中,本地主机是10.0.2.2
    【解决方案6】:

    我想,Android 无法设置 SSL 连接。也许您的其他主机名证书,而不是您建立连接的证书。阅读文档 herehere

    【讨论】:

    • 我已经设置了 SSL 连接,但在这里我清楚一件事是“也许证书是自签名证书并且不包括在 KeyStore 中。
    • android 不会接受任何没有正确编码的 android 版本中的自签名证书。您可以嗅探流量并查看握手期间出了什么问题。并在不同的安卓版本上进行测试。也许它会帮助你解决你的问题。
    【解决方案7】:

    您的问题可能是您的网址是通过“https”解决的。您必须将所有字符串 url 转换为“http”,它才会起作用。

    编辑:

    SchemeRegistry schemeRegistry = new SchemeRegistry ();
    
    schemeRegistry.register (new Scheme ("http",
        PlainSocketFactory.getSocketFactory (), 80));
    schemeRegistry.register (new Scheme ("https",
        new CustomSSLSocketFactory (), 443));
    
    ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager (
        params, schemeRegistry);
    
    return new DefaultHttpClient (cm, params);
    

    CustomSSLSocketFactory:

    public class CustomSSLSocketFactory extends org.apache.http.conn.ssl.SSLSocketFactory
    {
        private SSLSocketFactory FACTORY = HttpsURLConnection.getDefaultSSLSocketFactory ();
    
        public CustomSSLSocketFactory ()
        {
            super(null);
            try
            {
                SSLContext context = SSLContext.getInstance ("TLS");
                TrustManager[] tm = new TrustManager[] { new FullX509TrustManager () };
                context.init (null, tm, new SecureRandom ());
    
                FACTORY = context.getSocketFactory ();
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    
        public Socket createSocket() throws IOException
        {
            return FACTORY.createSocket();
        }
    
        // TODO: add other methods like createSocket() and getDefaultCipherSuites().
        // Hint: they all just make a call to member FACTORY 
    }
    

    FullX509TrustManager 是一个实现 javax.net.ssl.X509TrustManager 的类,但没有一个方法实际执行任何工作,请在 [此处][1] 获取示例。

    祝你好运!

    【讨论】:

    • 是不是无法通过 HTTPS url 建立连接..?
    【解决方案8】:

    这对我来说效果更好 --> CHANGING StrictHostnameVerifier()

    https://developer.android.com/reference/org/apache/http/conn/ssl/StrictHostnameVerifier

    例子

        HostnameVerifier hostnameVerifier = new HostnameVerifier() {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            HostnameVerifier hv = new StrictHostnameVerifier();
    
            return hv.verify("example.com", session);
           }
        };
    

    使用示例https://developer.android.com/training/articles/security-ssl#java

        // Tell the URLConnection to use our HostnameVerifier
        URL url = new URL("https://example.org/");
        HttpsURLConnection urlConnection = 
       (HttpsURLConnection)url.openConnection();
       urlConnection.setHostnameVerifier(hostnameVerifier);
    

    【讨论】:

      【解决方案9】:

      来自亚马逊文档: Bucket Restrictions

      “当使用带有 SSL 的虚拟托管式存储桶时,SSL 通配符证书仅匹配不包含句点的存储桶。要解决此问题,请使用 HTTP 或编写您自己的证书验证逻辑。”

      最简单的方法似乎是创建一个不带句点的唯一存储桶名称:

      而不是“bucketname.mycompany.com”,而是类似于“bucketnamemycompany”或任何其他符合 DNS 的存储桶名称。

      【讨论】:

        【解决方案10】:

        在 Kotlin 中:

        fun HttpsURLConnection.trustCert() {
                    try {
                        //Accepts every hostname
                        this.hostnameVerifier = HostnameVerifier { hostname, _ ->
                            println(hostname) //To be hardcoded/as needed
                            true
                        }
                        val trustMgr:Array<TrustManager> = arrayOf(object : X509TrustManager {
                            override fun checkClientTrusted(certs: Array<out X509Certificate>?, authType: String?) {}
                            override fun checkServerTrusted(certs: Array<out X509Certificate>?, authType: String?) {}
                            override fun getAcceptedIssuers(): Array<X509Certificate>? = null
                        })
                        this.sslSocketFactory = SSLContext.getInstance("TLS").also {
                            it.init(null, trustMgr, SecureRandom())
                        }.socketFactory
                    } catch (e: Exception) {
                        prinntln("SSL self-signed certificate processing error due to ${e.message}")
                    }
                }
        

        用法:

        val conn = URL(Uri.Builder().also { 
            it.scheme("https")
            it.encodedAuthority("$serverIp:$serverPort")
        }.build().toString()).openConnection() as HttpsURLConnection
        conn.trustCert()
        val respCode = conn.responseCode
        if(respCode == 200) {
            //do something (eg: read inputStream)
        }
        

        【讨论】:

          猜你喜欢
          • 2015-08-25
          • 2020-11-15
          • 1970-01-01
          • 1970-01-01
          • 2015-11-30
          • 2019-01-20
          • 2021-09-19
          • 2018-05-24
          • 1970-01-01
          相关资源
          最近更新 更多