43

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

代码片段:

    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 OS 谢谢。

全栈跟踪

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

10 回答 10

62

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

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

}

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

于 2013-03-06T16:02:16.260 回答
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();
    }
}

希望能帮助到你

于 2014-10-19T22:29:45.413 回答
11

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

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

于 2014-11-04T15:21:37.630 回答
7

我在 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 中将域作为虚拟主机会导致问题。

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

于 2013-07-08T17:13:55.267 回答
6

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

如果您使用 IP,请插入以下代码

HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier()
        {
            @Override
            public boolean verify(String hostname, SSLSession session)
            {
                if(hostname.equals("127.0.0.1"))
                     return true;
            }
        });
于 2018-05-15T13:52:22.183 回答
2

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

于 2013-01-31T06:44:15.067 回答
1

您的问题可能是您的网址是通过“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);

自定义SSLSocketFactory:

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] 获取示例。

祝你好运!

于 2013-01-31T07:44:16.283 回答
1

这对我来说效果更好--> 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);
于 2018-09-19T17:01:39.847 回答
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)
}
于 2020-11-17T11:00:26.267 回答
0

来自亚马逊文档: 存储桶限制

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

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

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

于 2015-10-08T21:36:30.083 回答