'No peer certificate' error in Android 2.3 but NOT in 4
Asked Answered
C

5

22

Getting the "javax.net.ssl.SSLPeerUnverifiedException: No peer certificate error" in an emulator running Android 2.3 but NOT in 4. In 4 it works perfectly. I'm trying to connect to a live server via https. It uses a valid Thawte certificate, works fine in all browsers and Android 3 and 4.

If anyone has code help, PLEASE and thanks. Also, if anyone has any suggestions on a secure workaround, I'd appreciate it. I'm still learning, and I've been on this problem for a week. It has to end, so I can continue working and learning. Urgh.

Here is HttpCLient code, courtesy Antoine Hauck (http://blog.antoine.li/2010/10/22/android-trusting-ssl-certificates/):

 import java.io.InputStream;
    import java.security.KeyStore;
    import java.security.cert.CertificateException;

    import javax.net.ssl.SSLContext;
    import javax.net.ssl.TrustManager;
    import javax.net.ssl.X509TrustManager;
    import javax.security.cert.X509Certificate;

    import org.apache.http.conn.ClientConnectionManager;
    import org.apache.http.conn.scheme.PlainSocketFactory;
    import org.apache.http.conn.scheme.Scheme;
    import org.apache.http.conn.scheme.SchemeRegistry;
    import org.apache.http.conn.ssl.SSLSocketFactory;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.apache.http.impl.conn.SingleClientConnManager;

    import android.content.Context;

    public class MyHttpClient extends DefaultHttpClient {

    final Context context;

    public MyHttpClient(Context context) {
        this.context = context;
    }

    @Override
    protected ClientConnectionManager createClientConnectionManager() {
        SchemeRegistry registry = new SchemeRegistry();
        registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        // Register for port 443 our SSLSocketFactory with our keystore
        // to the ConnectionManager
        registry.register(new Scheme("https", newSslSocketFactory(), 443));
        return new SingleClientConnManager(getParams(), registry);
    }

    private SSLSocketFactory newSslSocketFactory() {
         try {
             // Get an instance of the Bouncy Castle KeyStore format
             KeyStore trusted = KeyStore.getInstance("BKS");
             // Get the raw resource, which contains the keystore with
             // your trusted certificates (root and any intermediate certs)
             InputStream in = context.getResources().openRawResource(R.raw.my_cert);
             try {
                 // Initialize the keystore with the provided trusted certificates
                 // Also provide the password of the keystore
                 trusted.load(in, "my_pass".toCharArray());
             } finally {
                 in.close();
             }

            // Pass the keystore to the SSLSocketFactory. The factory is responsible
            // for the verification of the server certificate.
            SSLSocketFactory sf = new SSLSocketFactory(trusted);
            // Hostname verification from certificate
            // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506
            sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
            return sf;
        } catch (Exception e) {
            throw new AssertionError(e);
        }
    }
}

And here is the code that instantiates it:

DefaultHttpClient client = new MyHttpClient(getApplicationContext());

           HttpPost post = new HttpPost(server_login_url);
           List <NameValuePair> parameters = new ArrayList <NameValuePair>();
           parameters.add(new BasicNameValuePair("username", user));
           parameters.add(new BasicNameValuePair("password", pass));

            try {
               post.setEntity(new UrlEncodedFormEntity(parameters, HTTP.UTF_8));
            } catch (UnsupportedEncodingException e2) {
                // TODO Auto-generated catch block
                Log.d(DEBUG_TAG, "in  UnsupportedEncodingException - " + e2.getMessage());
                e2.printStackTrace();
            }
                // Execute the GET call and obtain the response
           HttpResponse getResponse = null;

            try {
                getResponse = client.execute(post);
            } catch (ClientProtocolException e) {
                // TODO Auto-generated catch block
                // Toast.makeText(getBaseContext(),message,Toast.LENGTH_LONG).show();
                Log.d(DEBUG_TAG, "in ClientProtocolException - " + e.getMessage());
            } catch (IOException e) {
                // TODO Auto-generated catch block
                // Toast.makeText(getBaseContext(),message,Toast.LENGTH_LONG).show();
                Log.d(DEBUG_TAG, "in  client.execute IOException - " + e.getMessage());
                e.printStackTrace();
            }

The error is caught in the IOException block. Here is the stack:

javax.net.ssl.SSLPeerUnverifiedException: No peer certificate
org.apache.harmony.xnet.provider.jsse.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:258)
org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:93)
org.apache.http.conn.ssl.SSLSocketFactory.createSocket(SSLSocketFactory.java:381)
org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:164)
org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:164)
org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:119)
org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:359)
org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:555)
org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:487)
org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:465)
org.ffb.tools.SplashActivity$LoginTask.makeConnection(SplashActivity.java:506)
org.ffb.tools.SplashActivity$LoginTask.doLogin(SplashActivity.java:451)
org.ffb.tools.SplashActivity$LoginTask.doInBackground(SplashActivity.java:439)
org.ffb.tools.SplashActivity$LoginTask.doInBackground(SplashActivity.java:1)
android.os.AsyncTask$2.call(AsyncTask.java:185)
java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:306)
java.util.concurrent.FutureTask.run(FutureTask.java:138)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1088)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:581)
java.lang.Thread.run(Thread.java:1019)

Here is the chain order (from openssl command):

The chain looks good I think.

    i:/C=US/O=Thawte, Inc./OU=Domain Validated SSL/CN=Thawte DV SSL CA
  1 s:/C=US/O=Thawte, Inc./OU=Domain Validated SSL/CN=Thawte DV SSL CA
  i:/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 2006 thawte, Inc. - For authorized      use only/CN=thawte Primary Root CA
  2 s:/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 2006 thawte, Inc. - For      authorized use only/CN=thawte Primary Root CA
  i:/C=ZA/ST=Western Cape/L=Cape Town/O=Thawte Consulting cc/OU=Certification Services      Division/CN=Thawte Premium Server CA/[email protected]
Correggio answered 5/3, 2012 at 22:14 Comment(0)
S
28

This thread was really helpful when I debugged a similar issue.

Summary Android 2.3 HTTPS/SSL checklist:

  • If your CA is in Android's 2.3 list of trusted CA's -- and Thawte is -- there's no need to include the certificate in the app.
  • Android 2.3 does not support Server Name Indication so if your server is relying on it for SSL handshaking, Android may not be getting the certificates you're expecting.
  • Do you have certificate chain on the server installed, and is it ordered correctly? Most browsers handle out-of-order certificate chains but Android 2.3 does not. bdc's answer in the thread I mentioned above describes how to check the validity of your SSL certificate and chain with "openssl s_client -connect yourserver.com:443".
  • When digging up that old 2.3 device you have in your bottom drawer, please ensure its date and time are set correctly after being powerless for too long.
Soosoochow answered 6/3, 2012 at 17:50 Comment(5)
Thanks, Ed. Added certificate chain to original post. I included both the intermediate then the root certificates in one keystore using the Java keytool. Is that the correct way to go about things?Correggio
If you're using Thawte you shouldn't need to include your certificate in the app. Thawte is in Android's 2.3 list of trusted CA's. Another thing to look out for is that Android 2.3 does not support Server Name Indication so if your server is relying on it Android may not be getting the certificates you're expecting.Soosoochow
Well won't this be funny when I look back on it a year from now. I spent two weeks fixing something that wasn't broken. Yes, Ed, you are right. The keystores were unnecessary; the thawte certificate is already trusted. It is working on my Android 4 phone and emulator running 2.3. Originally I got a certificate error (can't remember what it was), which is what led me down this path, but ... well I've learned a lot in the past 2 weeks anyway... Ed, if you update your original answer with your edit I can mark it as the answer. THANKS!!Correggio
#4 (wrong date/time) did the trick in my case. Sometimes you get caght in debuging code and forget to check simple things like that. Thanks.Livesay
#4 was the issue for me as well, the certificate simply wasn't valid for the date set on the device, which was inactive for a while. Thanks.Bikaner
E
3

I had exactly the same issue as you. Everything was working fine with android >3.X but when I tried with some (but not all !) 2.3.X devices I got that famous "No peer certificate error" exception.

I dug a lot through stackoverflow and other blogs but I haven't found anything that worked on those "rogue" devices (in my case: correct use of truststore; no sni required; correct cert chain order on server; etc ...).

It's look like that android's Apache HttpClient was just not working correctly on some 2.3.X devices. The "no peer certificates" exception was occurring too early to even reach a custom hostname verifier code, so solution like that one were not working for me.

Here was my code :

KeyStore trustStore = KeyStore.getInstance("BKS");
InputStream is = this.getAssets().open("discretio.bks");
trustStore.load(is, "discretio".toCharArray());
is.close();

SSLSocketFactory sockfacto = new SSLSocketFactory(trustStore);
sockfacto.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);

SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(new Scheme("https", sockfacto, 443));

SingleClientConnManager mgr = new SingleClientConnManager(httpParameters, schemeRegistry);

HttpClient client = new DefaultHttpClient(mgr, httpParameters);
HttpGet request = new HttpGet(url);
HttpResponse response = client.execute(request);

So I rewrote everything using javax.net.ssl.HttpsURLConnection and now it's working on all devices I have tested (from 2.3.3 to 4.X).

Here is my new code :

KeyStore trustStore = KeyStore.getInstance("BKS");
InputStream is = this.getAssets().open("discretio.bks");
trustStore.load(is, "discretio".toCharArray());
is.close();

TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(trustStore);
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);

URL request = new URL(url);
HttpsURLConnection urlConnection = (HttpsURLConnection) request.openConnection();

//ensure that we are using a StrictHostnameVerifier
urlConnection.setHostnameVerifier(new StrictHostnameVerifier());
urlConnection.setSSLSocketFactory(context.getSocketFactory());
urlConnection.setConnectTimeout(15000);

InputStream in = urlConnection.getInputStream();
//I don't want to change my function's return type (laziness) so I'm building an HttpResponse
BasicHttpEntity res = new BasicHttpEntity();
res.setContent(in);
HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, urlConnection.getResponseCode(), "");
resp.setEntity(res);

Certificate chain and hostname validation are working (I tested them). If anyone want to take a better look on the change, here is a diff

Comments are welcome, I hope it will help some people.

Evangelist answered 22/1, 2013 at 18:0 Comment(1)
This will work from 2.3 and upwards, but not on 2.2. See this answer to another question.Bladderwort
D
1

Another source of this message can be an invalid date/time setting, e.g. when using a device which has gone a few months without power. Quite trivial, but it can be hard to spot.

Drawtube answered 30/7, 2013 at 15:20 Comment(0)
J
0

What certificates are you loading from R.raw.my_cert? This error either speaks to having a misconfigured server -- not installing Thawte's primary and secondary intermediate CAs -- or you not loading and trusting the correct certificate chain.

Jube answered 6/3, 2012 at 17:9 Comment(2)
Am I to take that literally? I've been loading the keystore for the root, then I tried a keystore that contained the intermediate, then the root. I tried loading 5 different keystores that had different combinations and orders (hail Mary, anyone?). But that is what you were asking, correct? You're not suggesting I put the actual certificates in the raw directory, are you?Correggio
Edited... you're loading a keystore.Jube
B
0

Certificate verification (or more precisely - chain building) logic in (at least) Android 2.3 is faulty.

Here is what I have observed:

  • If the TLS server in it's certificate chain provides only server's certificate (non-selfsigned or selfsigned), then you can put server's certificate in keystore and verification will succeed.

  • If the TLS server in it's certificate chain provides also intermediate CA certificate, then in the keystore you must put only root CA certificate and make sure that the keystore does NOT contain server's and intermediate CA certificates (otherwise the verification will fail randomly).

  • If the TLS server in it's certificate chain provides intermediate and root CA certificates in the correct order, then you just have to make sure that root CA certificate is in the keystore (doesn't matter if server's and intermediate CA certificates are there).

So the "correct/reliable" way how to handle this is to include in the keystore only root CA certificates and blame server configuration for "No peer certificate" - in case server's certificate chain does not provide intermediate CA certificates or certificates are in incorrect order. You can test server using https://www.ssllabs.com/ssltest/.

Bolection answered 8/10, 2014 at 11:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.