转载自: http://www.trinea.cn/android/android-java-https-ssl-exception-2/

详细分析Android及Java中访问https请求exception(SSLHandshakeException, SSLPeerUnverifiedException)的原因及解决方法。
1、现象
用Android(或Java)测试程序访问下面两个链接。
https链接一:web服务器为jetty,后台语言为java。
https链接二:web服务器为nginx,后台语言为php。
链接一能正常访问,访问链接二报异常,且用HttpURLConnection和apache的HttpClient两种不同的api访问异常信息不同,具体如下:
(1) 用HttpURLConnection访问,测试代码如下:

HttpURLConnection访问https Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public static String httpGet ( String httpUrl ) {      BufferedReader input = null ;      StringBuilder sb = null ;      URL url = null ;      HttpURLConnection con = null ;      try {          url = new URL ( httpUrl ) ;          try {              con = ( HttpURLConnection ) url . openConnection ( ) ;              input = new BufferedReader ( new InputStreamReader ( con . getInputStream ( ) ) ) ;              sb = new StringBuilder ( ) ;              String s ;              while ( ( s = input . readLine ( ) ) != null ) {                  sb . append ( s ) . append ( "\n" ) ;              }          } catch ( IOException e ) {              e . printStackTrace ( ) ;          }      } catch ( MalformedURLException e1 ) {          e1 . printStackTrace ( ) ;      } finally {          // close buffered          if ( input != null ) {              try {                  input . close ( ) ;              } catch ( IOException e ) {                  e . printStackTrace ( ) ;              }          }          // disconnecting releases the resources held by a connection so they may be closed or reused          if ( con != null ) {              con . disconnect ( ) ;          }      }        return sb == null ? null : sb . toString ( ) ; }

异常信息为:

1 javax . net . ssl . SSLPeerUnverifiedException : No peer certificate

 

(2) 用apache的HttpClient访问,测试代码如下:

HttpClient访问https Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public static String httpGet ( String httpUrl ) {      HttpClient httpClient = new HttpClient ( ) ;      GetMethod httpGet = new GetMethod ( httpUrl ) ;        try {          if ( httpClient . executeMethod ( httpGet ) != HttpStatus . SC_OK ) {              // System.err.println("HttpGet Method failed: " + httpGet.getStatusLine());              return null ;          }          return httpGet . getResponseBodyAsString ( ) ;      } catch ( Exception e ) {          e . printStackTrace ( ) ;      } finally {          httpGet . releaseConnection ( ) ;          httpClient = null ;      }      return null ; }

异常信息为:

1 javax . net . ssl . SSLHandshakeException : sun . security . validator . ValidatorException : PKIX path building failed : sun . security . provider . certpath . SunCertPathBuilderException : unable to find valid certification path to requested target

 

 

2、原因分析
需要快速寻求答案的可直接看第3部分 解决方式,这部分详细分析原因。
google发现stackoverflow上不少人反应,twitter和新浪微博的api也会报这个异常,不少人反映客户端需要导入证书,其实大可不必,如果要导证书的话,用户不得哭了。。

 

从上面的情况可以看出,用jetty做为容器是能正常访问的,只是当容器为nginx时才会异常。

配合后台开发调试了很久,开始以为是cipher suite的问题,为此特地把
ssl_ciphers EDH-RSA-DES-CBC3-SHA;
加入了nginx的配置中,后来发现依然无效。stackoverflow发现,如下代码是能正常访问上面异常的https url

HttpURLConnection访问https并相信所有证书 Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 public static String httpGet ( String httpUrl ) {      BufferedReader input = null ;      StringBuilder sb = null ;      URL url = null ;      HttpURLConnection con = null ;      try {          url = new URL ( httpUrl ) ;          try {              // trust all hosts              trustAllHosts ( ) ;              HttpsURLConnection https = ( HttpsURLConnection ) url . openConnection ( ) ;              if ( url . getProtocol ( ) . toLowerCase ( ) . equals ( "https" ) ) {                  https . setHostnameVerifier ( DO_NOT_VERIFY ) ;                  con = https ;              } else {                  con = ( HttpURLConnection ) url . openConnection ( ) ;              }              input = new BufferedReader ( new InputStreamReader ( con . getInputStream ( ) ) ) ;              sb = new StringBuilder ( ) ;              String s ;              while ( ( s = input . readLine ( ) ) != null ) {                  sb . append ( s ) . append ( "\n" ) ;              }          } catch ( IOException e ) {              e . printStackTrace ( ) ;          }      } catch ( MalformedURLException e1 ) {          e1 . printStackTrace ( ) ;      } finally {          // close buffered          if ( input != null ) {              try {                  input . close ( ) ;              } catch ( IOException e ) {                  e . printStackTrace ( ) ;              }          }          // disconnecting releases the resources held by a connection so they may be closed or reused          if ( con != null ) {              con . disconnect ( ) ;          }      }        return sb == null ? null : sb . toString ( ) ; }   final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier ( ) {                                                    public boolean verify ( String hostname , SSLSession session ) {                                                      return true ;                                                  }                                              } ;   /** * Trust every server - dont check for any certificate */ private static void trustAllHosts ( ) {      final String TAG = "trustAllHosts" ;      // Create a trust manager that does not validate certificate chains      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 {              Log . i ( TAG , "checkClientTrusted" ) ;          }            public void checkServerTrusted ( X509Certificate [ ] chain , String authType ) throws CertificateException {              Log . i ( TAG , "checkServerTrusted" ) ;          }      } } ;        // Install the all-trusting trust manager      try {          SSLContext sc = SSLContext . getInstance ( "TLS" ) ;          sc . init ( null , trustAllCerts , new java . security . SecureRandom ( ) ) ;          HttpsURLConnection . setDefaultSSLSocketFactory ( sc . getSocketFactory ( ) ) ;      } catch ( Exception e ) {          e . printStackTrace ( ) ;      } }

可以看出其中与之前的HttpsURLConnection测试代码主要的不同就是加入了

1 trustAllHosts ( ) ;

1 https . setHostnameVerifier ( DO_NOT_VERIFY ) ;

表示相信所有证书,并且所有host name验证返回true,这样就能定位到之前的异常是证书验证不通过的问题了。

 

在上面checkServerTrusted函数中添加断点,查看X509Certificate[] chain的值,即证书信息,发现访问两个不同链接X509Certificate[] chain值有所区别,nginx传过来证书信息缺少了startssl 的ca证书,证书如下:

至此原因大白:
android的证书库里已经带了startssl ca证书,而nginx默认不带startssl ca证书,这样android端访问nginx为容器的https url校验就会失败,jetty默认带startssl ca证书,所以正常
PS:后来对windows和mac下java访问https也做了测试,发现mac上的jdk缺省不带startssl ca证书所以能访问通过,而加上startssl ca证书后同android一样访问不通过。而windows上的jdk缺省带startssl ca证书同android一样访问失败。

 

3、解决方式
上面的分析中已经介绍了一种解决方法即客户端相信所有证书,不过这种方式只是规避了问题,同时也给客户端带来了风险,比较合适的解决方式是为nginx添加startssl ca证书,添加方法如下:

First, use the StartSSL™ Control Panel to create a private key and certificate and transfer them to your server. Then execute the following steps (if you use a class 2 certificate replace class1 by class2 in the instructions below):

  • Decrypt the private key by using the password you entered when you created your key:

openssl rsa -in ssl.key -out /etc/nginx/conf/ssl.key

Alternatively you can also use the Tool Box decryption tool of your StartSSL™ account.

  • Protect your key from prying eyes:

chmod 600 /etc/nginx/conf/ssl.key

  • Fetch the Root CA and Class 1 Intermediate Server CA certificates:

wget http://www.startssl.com/certs/ca.pem
wget http://www.startssl.com/certs/sub.class1.server.ca.pem

  • Create a unified certificate from your certificate and the CA certificates:

cat ssl.crt sub.class1.server.ca.pem ca.pem > /etc/nginx/conf/ssl-unified.crt

  • Configure your nginx server to use the new key and certificate (in the global settings or a server section):

ssl on;
ssl_certificate /etc/nginx/conf/ssl-unified.crt;
ssl_certificate_key /etc/nginx/conf/ssl.key;

  • Tell nginx to reload its configuration:

killall -HUP nginx

也可以直接访问install startssl on nginx.


更多相关文章

  1. Android证书创建之 keytool 错误:java.io.IOException:Incorrect
  2. 【Android】自带Theme
  3. Android(安卓)网络链接,不要忘记添加网络权限。
  4. Android与js交互实例
  5. Android(安卓)4.0 访问WebService 出现 android.os.NetworkOnMai
  6. Android中TextView中加图片,超链接,部分字或者背景变色。。。不断
  7. Android指定调用系统自带浏览器打开链接
  8. ADT下载地址(含各版本)(转)
  9. Android安全加密:Https编程

随机推荐

  1. android中使用线程池和临时缓存优化网络
  2. 【边做项目边学Android】知识点:Adapter适
  3. Android(安卓)系统 目录 分析
  4. [Android(安卓)L]SEAndroid增强Androd安
  5. Android系统的Binder机制之一——Service
  6. Android(安卓)的上下文菜单: Context Menu
  7. [Android]获取未安装的APK图标
  8. android 邮件
  9. Android(安卓)判断应用 第一次启动
  10. android工程中不自动生成Android(安卓)De