java.io.IOException:未验证主机名

时间:2013-01-31 06:32:01

标签: android http

我正在尝试从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)                                

10 个答案:

答案 0 :(得分:58)

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

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;
    }

}

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

答案 1 :(得分:19)

除了@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();
    }
}

希望有所帮助

答案 2 :(得分:10)

可能会发生这种情况,因为您在SSL上声明的CN(公用名)也不会发送您发送HTTP请求的实际URL。

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

答案 3 :(得分:6)

我在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可以与多个子域并排作为虚拟主机使用,但在同一个apache中将 root 域作为虚拟主机会导致问题。< / p>

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

答案 4 :(得分:3)

请注意,SSL证书只能通过域工作,不能按IP地址工作。

如果您使用IP,请在代码

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

答案 5 :(得分:2)

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

答案 6 :(得分:1)

您的问题可能是通过“https”解决了您的网址问题。你必须将所有字符串网址转换为“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的类,但没有一个方法实际执行任何工作,得到一个样本[here] [1]。

祝你好运!

答案 7 :(得分:1)

这对我来说更好->更改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);

答案 8 :(得分:0)

来自亚马逊的文档: Bucket Restrictions

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

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

而不是&#34; bucketname.mycompany.com&#34;,类似&#34; bucketnamemycompany&#34;或任何其他符合DNS的存储桶名称。

答案 9 :(得分:0)

在科特林:

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)
}