2012-03-05 56 views
22

在運行Android 2.3但不是4的模擬器中獲取"javax.net.ssl.SSLPeerUnverifiedException: No peer certificate error"。我正試圖通過https連接到一個活的服務器。它使用有效的Thawte證書,在所有瀏覽器和Android 3和4中均可正常工作。Android 2.3中沒有對等證書錯誤,但不是4

如果任何人有代碼幫助,請致謝。此外,如果有人對安全解決方法有任何建議,我會很感激。我仍然在學習,而且我一直在解決這個問題一個星期。它必須結束,所以我可以繼續工作和學習。 Urgh。

這裏是HttpClient的代碼,禮貌安託萬·豪克(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); 
     } 
    } 
} 

這裏是實例化它的代碼:

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

錯誤是夾在IOException異常塊。這裏是堆棧:

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) 

這裏是鏈順序(從OpenSSL的命令):

鏈看起來不錯,我認爲。

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] 

回答

28

這個thread在調試類似問題時非常有用。

總結了Android 2.3 HTTPS/SSL清單:

  • 如果你的CA是Android的2.3 list of trusted CA's - 和Thawte是 - 有沒有必要在應用程序的證書。
  • Android 2.3不支持Server Name Indication因此,如果您的服務器依賴SSL握手,Android可能無法獲得您期待的證書。
  • 您是否在安裝的服務器上有證書鏈,並且它的順序是否正確?大多數瀏覽器處理亂序證書鏈,但Android 2.3不支持。在上面提到的線程中,bdc的答案描述瞭如何使用「openssl s_client -connect yourserver.com:443」來檢查SSL證書和鏈的有效性。
  • 當您在底部抽屜中挖掘出舊的2.3設備時,請確保在無力時間過長後才能正確設置其日期和時間。
+0

謝謝,編輯。向原始文章添加證書鏈。我使用Java keytool將中間和根證書都包含在一個密鑰庫中。這是正確的方式去做事嗎? – user1214401 2012-03-06 18:58:53

+0

如果您使用Thawte,則不需要在應用程序中包含您的證書。 Thawte在Android 2.3 [受信任的CA列表]中(http://www.andreabaccega.com/blog/2010/09/23/android-root-certification-authorities-list/)。另外需要注意的是,Android 2.3不支持[Server Name Indication](服務器名稱指示)(http://en.wikipedia.org/wiki/Server_Name_Indication),所以如果你的服務器依賴於它,Android可能不會獲得你的「重新期待。 – 2012-03-06 19:50:41

+0

從一開始我回顧一年後,這不會很有趣。我花了兩個星期的時間修復一些沒有損壞的東西。是的,埃德,你是對的。密鑰庫是不必要的;解凍證書已被信任。它正在運行我的Android 4手機和仿真器。最初我得到了一個證書錯誤(不記得它是什麼),這是什麼導致我走了這條路,但是......好吧,在過去的兩週裏我學到了很多......埃德,如果你更新你的編輯的原始答案我可以將其標記爲答案。謝謝!! – user1214401 2012-03-07 06:31:12

0

您從R.raw.my_cert加載了哪些證書?這個錯誤要麼是服務器配置不正確 - 不是安裝Thawte的主要服務器和中間服務器的中間服務器 - 或者您沒有加載和信任正確的證書鏈。

+0

我可以從字面上理解嗎?我一直在加載根密鑰庫,然後我嘗試了一個包含中間體,然後是根的密鑰庫。我嘗試了加載5個不同組合和命令的不同密鑰庫(冰雹瑪麗,任何人?)。 但這就是你問的,對嗎?你不是建議我把真正的證書放在原始目錄中,是嗎? – user1214401 2012-03-06 18:57:31

+0

已編輯...您正在加載密鑰庫。 – 2012-03-06 19:13:02

3

我和你有完全一樣的問題。一切工作正常與Android> 3.X,但當我嘗試了一些(但不是全部!)2.3.X設備,我得到了那個着名的「沒有對等證書錯誤」異常。

我通過stackoverflow和其他博客挖了很多,但我還沒有發現任何工作在這些「流氓」設備上的東西(在我的情況下:正確使用信任庫;不需要sni;正確的服務器上的證書鏈順序等...)。

這看起來像是Android的Apache HttpClient只是在某些2.3.X設備上無法正常工作。 「沒有對等證書」異常發生得太早,甚至沒有達到自定義主機名驗證碼,所以像that one這樣的解決方案對我而言並不適用。

這裏是我的代碼:

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

所以我重寫使用javax.net.ssl.HttpsURLConnection中的一切,現在,它的工作對我已經測試(從2.3.3到4.x)的所有設備。

這是我的新代碼:

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

證書鏈和主機名驗證正在努力(我對它們進行測試)。 如果有人想要更好地看待變化,這裏是一個diff

評論是歡迎的,我希望它會幫助一些人。

+0

這將從2.3開始,但不是2.2。請參閱[對另一個問題的此答案](http://stackoverflow.com/a/5895320/912936)。 – robpvn 2013-02-15 12:49:46

1

此消息的另一個來源可能是無效的日期/時間設置,例如,當使用已經停電幾個月的設備時。相當微不足道,但很難發現。

0

(至少)Android 2.3的證書驗證(或更確切地說 - 鏈式構建)邏輯有問題。

這是我觀察到:

  • 如果在它的TLS服務器的證書鏈只提供服務器的證書(非自簽名或自簽名),那麼你可以把服務器的證書密鑰存儲和驗證會成功。

  • 如果其證書鏈中的TLS服務器也提供了中間CA證書,那麼在密鑰庫中必須只放入根CA證書,並確保密鑰庫不包含服務器和中間CA證書(否則驗證將失敗隨機地)。

  • 如果其證書鏈中的TLS服務器以正確的順序提供中間和根CA證書,那麼您只需確保根CA證書在密鑰庫中(如果服務器和中間CA證書無關緊要在那兒)。

那麼「正確/可靠」的方式如何處理,這是在密鑰庫中唯一的根CA證書和指責服務器配置爲「沒有同行證書」包括 - 如果服務器的證書鏈不提供中間CA證書或證書的順序不正確。您可以使用https://www.ssllabs.com/ssltest/來測試服務器。

相關問題