Https证书校验不当引起的安全问题
2017-02-16 14:28
836 查看
1. 使用Webview进行HTTPs通信
Android系统内置了一些可信机构办法的证书,可用于作HTTPs证书校验。实际上,使用Webview组件进行HTTPs通信,其证书验证环节也是系统默认会去做的。若发现证书不合法,Webview将显示一个空白页面,其错误在onReceivedSslError()这个方法里进行处理。使用Webview进行HTTPs通信应当遵循如下安全规范:1) onReceivedSslError()方法里不能简单地用proceed()方法进行处理,建议给用户一定的提示(如“SSL证书错误,是否继续连接”等)。
这里给出错误的代码示例。
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error){ // 只简单地调用proceed()方法,忽略证书错误问题 paramSslErrorHandler.proceed(); }
一般来说,使用Webview连接带有可信机构颁发证书的HTTPs站点,onReceivedSslError()方法里无需作任何处理(系统默认是拒绝连接的),这里给出正确的示例代码。
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error){ // Do nothing // 效果同 paramSslErrorHandler.cancel(); }
2. X509TrustManager
X509TrustManager用于实现SSL证书的安全校验,若使用不当,将导致APP对SSL证书不作校验,从而使黑客有了中间人攻击的可乘之机。开发者经常犯的错误有如下: 自定义X509TrustManager,且不做任何校验逻辑,一般为空实现;
这里给出常见的自定义X509TrustManager的错误示例代码(以使用HttpsURLConnection进行HTTPs连接的情况为例)。
SSLContext localSSLContext = SSLContext.getInstance("TLS"); TrustManager[] arrayOfTrustManager = new TrustManager[1]; // 自定义X509TrustManager arrayOfTrustManager[0] = new MyTrustManager(); localSSLContext.init(null, arrayOfTrustManager, new SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(localSSLContext.getSocketFactory()); HttpsURLConnection localHttpsURLConnection = (HttpsURLConnection) new URL(paramString).openConnection(); …… class MyTrustManager implements X509TrustManager{ // 不作任何校验 public void checkClientTrusted(X509Certificate[] paramArrayOfX509Certificate, String paramString) {} // 不作任何校验 public void checkServerTrusted(X509Certificate[] paramArrayOfX509Certificate, String paramString) {} // 返回null public X509Certificate[] getAcceptedIssuers(){ return null; } }
使用上述方式进行HTTPs通信,将存在中间人攻击的可能。由于缺乏证书校验机制,黑客可以通过使用自签名证书,结合DNS欺骗,使用户访问恶意页面。因此,使用HttpsURLConnection或HttpClient进行HTTPs通信时,需要验证证书的合法性。这里把HTTPs站点作分类:
A. 带有可信机构颁发证书的HTTPs站点;
B. 带有自签名证书的HTTPs站点。
对于A类站点,可借助Android系统自带的证书校验机制,开发者无需自己实现任何代码;对于B类站点,则需要把证书文件内置到apk中,根据证书keystore得到用于校验证书内容的TrustManager,并设置在SSLContext当中。
使用HttpsURLConnection或HttpClient进行HTTPs通信,需要遵循如下安全规范:
1) 访问A类站点时,不要自定义X509TrustManager;
2) 访问B类站点时,把证书文件打包到apk中,并根据证书的keystore生成TrustManager。
这里给出使用HttpsURLConnection访问A类站点时的示例代码。
SSLContext localSSLContext = SSLContext.getInstance("TLS"); // 不要自定义X509TrustManager localSSLContext.init(null, null, new SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(localSSLContext.getSocketFactory()); HttpsURLConnection localHttpsURLConnection = (HttpsURLConnection) new URL(paramString).openConnection(); 这里给出使用HttpsURLConnection访问B类站点时的示例代码。 // 根据证书文件生成keystore private KeyStore certTrusted(Context context) throws Exception { // 从资源文件中获取.cer证书文件 AssetManager am = context.getAssets(); InputStream ins = am.open("12306.cer"); try { // 读取证书 CertificateFactory cerFactory = CertificateFactory.getInstance("X.509"); java.security.cert.Certificate cer = cerFactory.generateCertificate(ins); // 创建一个证书库,并将证书导入证书库 KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC"); keyStore.load(null, null); keyStore.setCertificateEntry("12306", (java.security.cert.Certificate) cer); return keyStore; } finally { ins.close(); } } …… // 获取keystore KeyStore keystore = certTrusted(this); SSLContext sc = SSLContext.getInstance("TLS"); // 根据keystore初始化TrustManagerFactory String algorithm = TrustManagerFactory.getDefaultAlgorithm(); TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm); tmf.init(keystore); // 得到设置好的TrustManager,初始化SSLContext sc.init(null, tmf.getTrustManagers(), new SecureRandom()); // 注意这里的SSLSocketFactory是javax.net.ssl包中的 HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); HttpsURLConnection conn = (HttpsURLConnection) new URL(paramString).openConnection(); ……
使用HttpClient与HttpsURLConnection略有不同,简单来说,HttpClient中使用到的SSLSocketFactory是org.apache.http.conn.ssl包中的,并非java原生的。
下面给出使用HttpClient连接A类站点的示例代码。
首先,实现MySSLSocketFactory类:
import java.io.IOException; import java.net.Socket; import java.net.UnknownHostException; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; // 注意这里的SSLSocketFactory是org.apache.http.conn.ssl包中的 import org.apache.http.conn.ssl.SSLSocketFactory; public class MySSLSocketFactory extends SSLSocketFactory { SSLContext sslContext = SSLContext.getInstance("TLS"); public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException,KeyManagementException, KeyStoreException, UnrecoverableKeyException { super(truststore); sslContext.init(null, null, new SecureRandom()); } @Override public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); } @Override public Socket createSocket() throws IOException { return sslContext.getSocketFactory().createSocket(); } }
然后,再实现MyHttpClient类:
import java.security.KeyStore; import org.apache.http.HttpVersion; import org.apache.http.client.HttpClient; 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.tsccm.ThreadSafeClientConnManager; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.apache.http.params.HttpProtocolParams; import org.apache.http.protocol.HTTP; public class MyHttpsClient { private static final int SET_CONNECTION_TIMEOUT = 50 * 1000; private static final int SET_SOCKET_TIMEOUT = 200 * 1000; public static HttpClient getNewHttpClient() { try { // 获取系统默认的KeyStore对象 KeyStore trustStore = KeyStore.getInstance(KeyStore .getDefaultType()); // 使用系统默认的KeyStore对象初始化SSLSocketFactory // 注意这里的SSLSocketFactory是org.apache.http.conn.ssl中的 SSLSocketFactory sf = new MySSLSocketFactory(trustStore); HttpParams params = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(params, 60000); HttpConnectionParams.setSoTimeout(params, 60000); HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); HttpProtocolParams.setContentCharset(params, HTTP.UTF_8); SchemeRegistry registry = new SchemeRegistry(); registry.register(new Scheme("http", PlainSocketFactory .getSocketFactory(), 80)); // 注意为https协议绑定之前定义的SSLSocketFactory对象 registry.register(new Scheme("https", sf, 443)); ClientConnectionManager ccm = new ThreadSafeClientConnManager( params, registry); HttpConnectionParams.setConnectionTimeout(params, SET_CONNECTION_TIMEOUT); HttpConnectionParams.setSoTimeout(params, SET_SOCKET_TIMEOUT); HttpClient client = new DefaultHttpClient(ccm, params); return client; } catch (Exception e) { return new DefaultHttpClient(); } } }
最后,使用下面的代码访问A类站点:
HttpClient hc = MyHttpsClient.getNewHttpClient(); HttpGet hg = new HttpGet(httpsURL); HttpResponse response = hc.execute(hg);
最后,使用下面的代码访问B类站点:
// 根据证书文件生成keystore private KeyStore certTrusted(Context context) throws Exception { // 从资源文件中获取.cer证书文件 AssetManager am = context.getAssets(); InputStream ins = am.open("12306.cer"); try { // 读取证书 CertificateFactory cerFactory = CertificateFactory.getInstance("X.509"); java.security.cert.Certificate cer = cerFactory.generateCertificate(ins); // 创建一个证书库,并将证书导入证书库 KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC"); keyStore.load(null, null); keyStore.setCertificateEntry("12306", (java.security.cert.Certificate) cer); return keyStore; } finally { ins.close(); } } …… // 获取keystore KeyStore keystore = certTrusted(this); // keystore对象作为getNewHttpClient的参数 HttpClient hc = MyHttpsClient.getNewHttpClient(keystore); HttpGet hg = new HttpGet(httpsURL); HttpResponse response = hc.execute(hg); ……
3. HostnameVerifier
HostnameVerifier用于实现HTTPs通信中的域名安全校验,即验证当前连接的HTTPs站点的SSL证书中的域名是否等于站点本身的域名。若使用不当,将导致APP对域名不作校验,从而使黑客有了中间人攻击的可乘之机。开发者经常犯的错误有如下: 自定义HostnameVerifier,且不做任何校验逻辑,一般为return true;
使用Android系统中自带的不安全的HostnameVerifier,效果等同于不做任何校验逻辑:
org.apache.http.conn.ssl.AllowAllHostnameVerifier
org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER
这里给出常见的自定义HostnameVerifier的错误示例代码(以使用HttpsURLConnection进行HTTPs连接的情况为例)。
// 自定义HostnameVerifier HttpsURLConnection.setDefaultHostnameVerifier(new MyHostnameVerifier()); HttpsURLConnection localHttpsURLConnection = (HttpsURLConnection) new URL(paramString).openConnection(); …… class MyHostnameVerifier implements HostnameVerifier { @Override // 不作任何校验 public boolean verify(String arg0, SSLSession arg1) { return true; } }
这里给出使用Android系统中自带的不安全的HostnameVerifier的错误示例代码(以使用HttpsURLConnection进行HTTPs连接的情况为例)。
URL url = new URL("https://www.example.com/"); HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection(); urlConnection.setHostnameVerifier(org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); InputStream in = urlConnection.getInputStream();
使用上述方式进行HTTPs通信,将存在中间人攻击的可能。由于缺乏域名校验机制,黑客可以通过使用自签名证书,结合DNS欺骗,使用户访问恶意页面。因此,使用HttpsURLConnection或HttpClient进行HTTPs通信时,需要验证域名的合法性。
使用HttpsURLConnection或HttpClient进行HTTPs通信,需要遵循如下安全规范:
1) 不要自定义HostnameVerifier;
2) 不要使用如下Android系统中自带的不安全HostnameVerifier:
org.apache.http.conn.ssl.AllowAllHostnameVerifier
org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER
3) 使用Android系统中自带的安全HostnameVerifier:
org.apache.http.conn.ssl.SSLSocketFactory.STRICT_HOSTNAME_VERIFIER
这里给出安全的HostnameVerifier的示例代码。
URL url = new URL("https://www.example.com/"); HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection(); urlConnection.setHostnameVerifier(org.apache.http.conn.ssl.SSLSocketFactory.STRICT_HOSTNAME_VERIFIER); InputStream in = urlConnection.getInputStream();
相关文章推荐
- WebView加载https链接的安全校验问题【7.0手机验证证书无法加载出带证书https页面】
- 一个空行引起的阿里云负载均衡上部署https证书的问题
- Android开发中关于Xwalkview加载https网页出现安全证书ssl问题
- IE7访问HTTPS网站提示证书有安全问题的解决方案
- HttpClient执行https链接证书安全问题
- Https信任证书申请与非信任证书生成方式,适用于TLS双向安全校验
- 关于如何取消访问https时的提示:“此网站的安全证书存在问题”的解决方法
- 解决GoAgent打开https网站SSL证书错误 (安全证书不受信任)问题
- APP中https证书有效性验证引发安全问题(例Fiddler可抓https包)
- 解决https网站根证书不安全的问题
- 测试需要修改系统时间之后, 引起https证书过期问题
- win8 ie10 输入https://localhost:1158/em 由于安全证书问题不能访问
- HTTPS访问站点,出现证书问题解决(转载)
- Httpclient的会话保持引起的线程安全问题
- 外包 WCF 安全通讯的例子代码,WCF 电子证书,HTTPS方式运行的例子代码
- 如何把Https网站中的安全证书导入到java中的cacerts证书库
- 20120407(安全证书问题)
- 调用Https WebService发布后使用时报“基础连接已经关闭: 未能为 SSL/TLS 安全通道建立信任关系”证书验证失败的解决过程(3)
- HTTPS访问站点,出现证书问题解决
- 将Capicom调用代码封装到ActiveX——解决javascript调Capicom读取数字证书信息时,IE弹出安全提示的问题