'No peer certificate' error in Android 2.3 but NOT in 4

前端 未结 5 1577
走了就别回头了
走了就别回头了 2020-11-30 01:15

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\

5条回答
  •  佛祖请我去吃肉
    2020-11-30 01:47

    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.

提交回复
热议问题