对于httpclient在android5.0及以下的系统中,访问多证书的服务器时出现hostname验证不过

现象:对于现在服务器部署的域名可多个后,在使用https协议时,会部署多个ssl证书链,而在android5.0及以下的系统使用httpclient访问这类某个域名时,有时候会出现hostname校验不过的情况,报的错误是

Certificate for < host > doesn't match common name of the certificate subject:   cn

host:是要访问的域名地址

cn:是证书中的域名地址


分析:对此我尝试用浏览器去打开这个域名,发现能成功访问并没有报错,对于应用层来说并不需要改变什么,那么问题应该出在SSL协议层,为了验证这个想法,我对机器ROOT后采用了tcpdump进行捉包,也可以在笔记本用fiddler,wireshark类软件捉包。


对于SSL协议中的四次握手,这里就不做详细说明,想了解可以去查阅相关的资料,还是很多的,


首次连接,客户端会向服务器发送一个clientHello,里面包含了协议版本号,客户端支持的加密算法列表(身份验证算法,生成传输 密钥的算法,使用传输密钥加密的算法,MAC完整性校验),compression方法,extension扩展域。


这是使用httpclient访问中捉出的SSL报文,访问结果hostname验证不过



这是使用浏览器成功访问截取的clientHello,


对比发现有2个地方不同,1)客户端支持的加密算法列表;2)extension域


因为可以看到ServerHello中最终返回的加密套件都是一样,因此2者的请求中都包含这个结果,同时也经过测试发现客户端的clientHello的加密算法域只传一个主流使用的也是能成功访问的,因此问题就出现的extension域了。


查询了相关资料发现,对于extension域,SNI已经是常见的字段,说起这个字段的缘由:在早期的SSL协议中并没有这个字段,因为那时候都是一个服务器对应一个域名的,所以当服务器支持多域名时,在SSL握手时,服务器并不知道客户端想访问的具体是哪个域名(只有到了http协议中的host域中才有具体的域名),因此服务器会返回默认的第一个配置的证书给客户端,如果此时访问的域名是A,第一个配置的证书是A,客户端是可以正常校验通过的;如果访问的是域名B,则会出现域名校验不过,也就是文中现象所指的问题。


因此只要在httpClient中让其ClientHello报文支持SNI即可解决这个问题。


对于android SDK自带的httpClient(版本很旧了),需要继承org.apache.http.conn.ssl.SSLSocketFactory类,并重写public Socket createSocket(Socket arg0, String arg1, int arg2, boolean arg3)方法

Socket socket = sslContext.getSocketFactory().createSocket(arg0, arg1,
				arg2, arg3);                                                                 try {
	java.lang.reflect.Method setHostnameMethod = ((SSLSocket) socket).getClass()
			.getMethod("setHostname", String.class);
	setHostnameMethod.invoke(((SSLSocket) socket), arg1);
	Log.i("haha", "sslSetHostNameOk");
} catch (Exception ex) {
 ex.printStackTrace();
}                     return socket;

通过SSLSocket的setHostname方法可以添加SNI字段,因为有些版本的SDK并没有开放这个方法, 因此采用类映射的方法调用,然后重新编译运行,即可成功访问。


android SDK中自带的httpClient确实太旧了,问题一堆,后续android6.0也移除了这部分的代码,google不愿去做维护了,因此我猜想新的httpClient包是否已经解决这个问题呢,我下载了httpclient-android-4.3.5.1.jar并导入项目中使用(不能使用httpClient.jar,httpCore.jar那几个包,里面的类和android SDK里面的重复了,系统会优先加载系统的类,导致运行错误)

因为httpclient-android-4.3.5.1.jar已不再使用DefaultHttpClient,而是使用 HttpClients.custom().build()生成CloseableHttpClient,基本用法不变,但对于HTTPS的配置部分,使用的不再是SSLSocketFactory,而是SSLConnectionSocketFactory这个类,在使用系统default类的情况下,依然会出现hostname校验不过,发现这个最新的jar并没有把SNI加上,因为也是采用重写SSLConnectionSocketFactory这个类的

public Socket connectSocket(int arg0, Socket arg1, HttpHost arg2,InetSocketAddress arg3, InetSocketAddress arg4, HttpContext arg5)

方法去映射调用setHostname方法去设置SNI的值。


对于网上也有很多建议使用自定义TrustManager,重写checkServerTrust时空实现,来达到对于服务器返回任何的证书都允许通过,对此是十分不建议的,首先这容易被中间人攻击,是不安全的。如果非要使用自定义TrustManager,一定要实现checkServerTrust的方法,对服务器返回的证书进行有效性验证,如果客户端访问的域名是固定的,也可以预先把服务器的ca证书放到asset中,进行强校验,只有和asset中的ca证书匹配才能校验通过,如果只是正常的校验的只要保证1)域名校验;2)证书有效性校验即可

对于1)中的域名校验,可在创建SSLSocket时,

((SSLSocket)socket).startHandshake();
SSLSession session = ((SSLSocket) socket).getSession();
if (session == null) {
	throw new SSLException("Cannot verify SSL socket without session");
}
if (!HttpsURLConnection.getDefaultHostnameVerifier().verify(arg2.getHostName(),session)) {
	throw new SSLPeerUnverifiedException("Cannot verify hostname: "+ arg1);
}


使用上述方法对SSLSocket链路中返回的证书进行域名校验。


这是使用httpClient遇到的一点问题,httpClient资源库使用确实方便,不过确实太旧了,如果有更好的建议欢迎告知。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值