44

I am trying to connect to a URL from a my Android app in Andorid Version 4.1.1, and I get the error indicated in the Title of my question, but when I tried to connect the same URL from Andorid Version 4.0.4 or 3.1, all works fine.

The Code fragment :

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

But here i clear one thing is that "Maybe certificate is that self-signed certificates and is not including them in a KeyStore.

I do not understand why this excepton occure only in Android Verison 4.1.1 OS Thanks.

FULL STACK TRACE

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)                                
Palak
  • 2,165
  • 2
  • 21
  • 31
  • What is the URL? http or https? domain name or id address? – Paresh Mayani Jan 31 '13 at 06:37
  • do you have multiple virtual hosts with different certificates? I am currently seeing this problem in 4.1.1 and 4.1.2 - I think because the server I am dealing with has multiple virtual hosts serving https traffic and possibly because of issues with Android and SNI - see also this bug https://code.google.com/p/android/issues/detail?id=36599 – Stevie Jul 08 '13 at 16:16
  • I suffered this problem few day ago, – Palak Jul 18 '13 at 08:39
  • My application can not download some file in Android 4.1.1 OS, but same code working in other Android OS(3.x, 4.0.x, 4.1.2). And I got solution, In my app one method trustAllHosts() do trust the certificate using TrustManager[] class. In this function I added 'HttpsURLConnection.setDefaultHostnameVerifier(DO_NOT_VERIFY);' And DO_NOT_VERIFY is object of HostnameVerifier class final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) { return true; } }; – Palak Jul 18 '13 at 08:48

9 Answers9

65

In case you are running with certificates that doesn't mean anything and you want to bypass them you also need to add a null host name verifier to make this code work

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

And the code for the host:

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

}

This needs to run once, but if you are making changes to your connection object you might need to run it again.

Keith John Hutchison
  • 4,955
  • 11
  • 46
  • 64
Noam
  • 1,022
  • 8
  • 8
20

In addition to @Noam's answer, this is a complete example:

/**
 * 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();
    }
}

Hope it helps

Matias Elorriaga
  • 8,880
  • 5
  • 40
  • 58
  • 9
    Its very bad to dsiable all SSL checking, its better to strip SSL completely and use HTTP, since you hardly have any security this way. – TjerkW Jun 01 '15 at 14:28
  • Works. Thanks Matias, valued hack to get by with dev unsigned certs, – sobelito Oct 25 '15 at 08:28
12

This might happen because the CN (Common Name) you have declared on your SSL does not mach the actual URL you are sending your HTTP request too.

If so, create a new SSL and enter the currect CN. That should fix the problem.

Yaki Klein
  • 3,978
  • 3
  • 37
  • 34
  • Is there anyway to solve this without creating a new ssl cert? I want to maintain the cert, but i can only use the currently different hostname. Perhaps there is a way to configure the server to serve that? – johan.i.zahri Jun 25 '15 at 07:31
  • From making a fast google search seems like it is not possible to change the cn after the certificate was issude. Take a look at this https://support.globalsign.com/customer/portal/articles/1215266-change-common-name-after-issuance---ssl-certificates – Yaki Klein Jun 25 '15 at 13:31
  • 1
    Wrong CN, because the stupid keytool asks for "what is your first name and last name" but it should be "what the * is your host name". see http://stackoverflow.com/questions/2200088/keytool-set-hostname – Martin Jun 26 '16 at 22:08
8

Please note SSL Certificate work only by Domain not work by IP address.

if you use IP ,insert below code

HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier()
        {
            @Override
            public boolean verify(String hostname, SSLSession session)
            {
                if(hostname.equals("127.0.0.1"))
                     return true;
            }
        });
Ali Bagheri
  • 3,068
  • 27
  • 28
7

I experienced this problem in 4.1.1 and 4.1.2, using HTTPSUrlConnection.

After some poking around I discovered that it was because the Apache server I am dealing with has multiple virtual hosts serving https traffic, resulting in SNI issues in Android - at least prior to JellyBean (I have unconfirmed reports that it was working in JB).

In my case there were 3 virtual hosts serving https traffic:

  • mydomain.com
  • api.mydomain.com (the one I was trying to deal with)
  • admin.mydomain.com

Probing api.* with openssl_client like this:

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

... always returned the root domain's certificate - buried in the output was something like:

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

... specifying the server name in the openssl_client command-line:

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

... returned the certificate I was expecting to see:

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

I was able to resolve the problem by moving the root domain virtual-host to a different physical host.

It seems that the Android HostnameVerifier can live with multiple sub-domain's side-by-side as virtual hosts, but having the root domain as a virtual-host in the same apache caused issues.

I am not a sys-admin/dev-ops and so it is possible that there are Apache config options that could have resolved the problem that I am not aware of.

Stevie
  • 7,957
  • 3
  • 30
  • 29
1

Android can't set up SSL connection, I suppose. Maybe your certificate for other host name, not the one you establish connection to. Read docs here and here.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Leonidos
  • 10,482
  • 2
  • 28
  • 37
  • I already set up SSL connection but here i clear one thing is that "Maybe certificate is that self-signed certificates and is not including them in a KeyStore. – Palak Jan 31 '13 at 07:43
  • android will not accept self-signed sertificates in any android version without proper coding. You can sniff traffic and see what went wrong during handshake. And test it on different android versions. Maybe it will help you to solve your problem. – Leonidos Jan 31 '13 at 07:59
1

This works better for me --> CHANGING StrictHostnameVerifier()

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

Example

    HostnameVerifier hostnameVerifier = new HostnameVerifier() {
    @Override
    public boolean verify(String hostname, SSLSession session) {
        HostnameVerifier hv = new StrictHostnameVerifier();

        return hv.verify("example.com", session);
       }
    };

Use Example 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);
user3826696
  • 1,045
  • 12
  • 13
0

From Amazon documentation: Bucket Restrictions

"When using virtual hosted–style buckets with SSL, the SSL wild card certificate only matches buckets that do not contain periods. To work around this, use HTTP or write your own certificate verification logic."

The easiest way seems to create a unique bucket name without periods:

Instead of "bucketname.mycompany.com", something like "bucketnamemycompany" or any other DNS-compliant bucket name.

Julian
  • 2,490
  • 24
  • 20
0

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

Usage:

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