Android 5.0以下系统支持TLS 1.1/1.2协议版本_Eric_Bang的博客-程序员ITS203

技术标签: handshake  android  Android  

一、背景

项目中,客户端与服务端之间普遍使用Https协议通信,突然接到测试同事反馈Android5.0以下手机上,App测试服使用出现问题,出现SSL handshake aborted错误信息,但正式服正常。经查,普遍错误信息详情如下:

 

SSL handshake aborted: ssl=0x78f080: I/O error during system call, Connection reset by peer

或者:
javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x78a8df68: Failure in SSL library, usually a protocol error

从错误信息上粗略看上去,SSL握手阶段出现问题,连接终止。



二、分析与处理

2.1 问题分析

从总体信息上看,显然测试服与正式服环境有所不同,导致在Android 5.0以下机型上SSL握手阶段失败。很有可能是测试服改变了相关配置。网上查了一圈,很快,Android官网文档上找到了对应指引。

developer.android.com/reference/j…

文档中的图示给出了Android版本与SSL/TLS版本之间的对应关系。

SSLSocket来源于javax.net.ssl,实际上是java的扩展库,Android不同系统版本中引入的是不同的Open JDK版本。因此,此处的版本对应关系的背后,实际上是因为java的不同版本中,对于SSLSocket中对SSL/TLS版本的默认支持发生了变化。

同时,对于更底层的,如SSLSocketFactoryImpl、SSLSocketImpl等实现类,Oracle JD是在sun.security.ssl包中,Open JDK对其进行了自己的实现,并放在了com.android.org.conscrypt包中。并在类名前前统一加上了Open加以区分,如OpenSSLSocketFactoryImplOpenSSLSocketImpl等。

对于conscrypt的介绍,可以参考文档:
source.android.google.cn/devices/arc…

Android 5.0 API级别是21,5.0以下常用的机型是4.4.x/4.2.x等。API Level 16对应的是Android 4.1。因此,问题基本上可以定位在服务端对TLS版本做了升级。

通过Https通信,客户端与服务端在SSL/TLS层建立安全连接前,涉及到版本协商过程。SSL/TLS在客户端和服务端分别具有对应的版本,握手阶段客户端与服务端的SSL/TLS版本,会取用两者同时支持的最高版本。如在Android 4.4手机上,默认支持SSLv3,TLSv1,如果服务端配置支持的协议版本是TLSv1,TLSv1.1,TLSv1.2,则会取用TLSv1作为协商后的版本。当然,无论是客户端还是服务端,对于SSL/TLS版本,在对应系统版本所能支持的协议版本范围内,是可以人为去修改的。如4.4系统手机上,可以将客户端在请求时的支持版本改成SSLv3,TLSv1,TLSv1.1,TLSv1.2。如果此时服务端支持的协议版本是TLSv1,TLSv1.1,TLSv1.2,协商后的版本将是TLSv1.2。

对于给定的Https的服务端网址,可以检测其当前所支持的SSL/TLS版本。

推荐一个非常实用的检测网站,不仅列出了服务端当前支持的版本,还列出了具体的加密套件等有用信息。

https://www.ssllabs.com/ssltest/analyze.html

对项目中的正式服和测试服实测,结果如下:
正式服:

测试服:

显然,服务端在测试服中,将TLS1.0的版本支持给直接去掉了。这也正好与测试结果及从Android官方文档中分析结果是一致的。

经与服务端/运维等同事确认,测试服TLS版本协议确实做了修改,处于安全及升级等方面考虑,测试服运行一段时间后,后续也会同样部署到正式服中。

这也就意味着,客户端是需要适配的。


2.2 问题处理

当前项目最低支持版本是4.4,从Android官方文档中可以看出,Android 4.4默认支持的SSL/TLS版本是SSLv3,TLSv1。但TLSv1.1,TLSv1.2实际上也是在其支持范围内的,需要人为去配置。

我们的最终目标是改变改变SSLSocket实例中的enabledProtocols,具体可以通过调用其方法setEnabledProtocols(String protocols[])SSLSocket,对外,是通过SSLSocketFactory接口的方式与外部交互,其创建的调用方式,具体是通过createSocket()方法进行。

因此,我们可以通过代理模式,设置兼容的SSLSocketFactory,并重写其对应的createSocket()方法,同时,将其设置给OkHttpClientsslSocketFactory

首先实现代理类:

public class TlsCompatSocketFactory extends SSLSocketFactory {
    
    private static final String[] TLS_VERSION_LIST = {
    "TLSv1", "TLSv1.1", "TLSv1.2"};

    final SSLSocketFactory target;

    public TlsCompatSocketFactory(SSLSocketFactory target) {
    
        this.target = target;
    }

    @Override
    public String[] getDefaultCipherSuites() {
    
        return target.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
    
        return target.getSupportedCipherSuites();
    }

    @Override
    public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
    
        return supportTLS(target.createSocket(s, host, port, autoClose));
    }

    @Override
    public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
    
        return supportTLS(target.createSocket(host, port));
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
    
        return supportTLS(target.createSocket(host, port, localHost, localPort));
    }

    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
    
        return supportTLS(target.createSocket(host, port));
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
    
        return supportTLS(target.createSocket(address, port, localAddress, localPort));
    }

    private Socket supportTLS(Socket s) {
    
        if (s instanceof SSLSocket) {
    
            ((SSLSocket) s).setEnabledProtocols(TLS_VERSION_LIST);
        }
        return s;
    }
}

然后在OkHttpClient的封装类中,将其设置给OkHttpClient的builder:

....
@JvmStatic
// 设置5.0以下机型可以支持TLS 1.1/1.2版本
val sc = SSLContext.getInstance("TLS")
sc.init(null, null, null)
clientBuilder.sslSocketFactory(TlsCompatSocketFactory(sc.socketFactory), object : X509TrustManager {
    
    @Throws(CertificateException::class)
    override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
    

    }

    @Throws(CertificateException::class)
    override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
    

    }

    override fun getAcceptedIssuers(): Array<X509Certificate> {
    
        return arrayOf()
    }
})
....

设置完成后,如下图所示,在进行Https请求时,sslSocketFactory已经被替换成了我们自己定义的代理类TlsCompatSocketFactory。其内部的target对象中的sslParametersenabledProtocols为TLSv1和SSlv3,此参数为创建SSLSocket对象时默认的SSL/TLS协议版本。现在通过代理后,SSLSocket对象中的enabledProtocols已经变更成我们自定义的TLS_VERSION_LIST,即同时包含了TLSv1、TLSv1.1、TLSv1.2协议版本。

另外,SSL/TLS协议版本中,还有一点需要注意的是,除了SSLSocket对象支持的协议版本外,OkHttp还通过connectionSpecs指定了一个连接规格连接规格中,包含有tlsVersions,此参数与SSLSocket中的enabledProtocols一起,用来控制实际连接建立时的终端SSL/TLS协议版本。

默认的取值逻辑如下:

static final List<ConnectionSpec> DEFAULT_CONNECTION_SPECS = Util.immutableList(
      ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT);

其中,MODERN_TLS对应的实现如下:

public static final ConnectionSpec MODERN_TLS = new Builder(true)
      .cipherSuites(APPROVED_CIPHER_SUITES)
      .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0)
      .supportsTlsExtensions(true)
      .build();

也就是说,默认情况下,ConnectionSpec中的tlsVersions对当前主流的SSL/TLS协议版本都是支持的。当然,特殊情况下,我们也可以人为去设置ConnectionSpec并指定其内部的tlsVersions

下面我们看下tlsVersionsenabledProtocols取交集的具体逻辑。

private fun supportedSpec(sslSocket: SSLSocket, isFallback: Boolean): ConnectionSpec {
    
    ....

    val tlsVersionsIntersection = if (tlsVersionsAsString != null) {
    
      sslSocket.enabledProtocols.intersect(tlsVersionsAsString, naturalOrder())
    } else {
    
      sslSocket.enabledProtocols
    }
    
    ....
    
    return Builder(this)
        .cipherSuites(*cipherSuitesIntersection)
        .tlsVersions(*tlsVersionsIntersection)
        .build()
}


 /** Applies this spec to {@code sslSocket}. */
  void apply(SSLSocket sslSocket, boolean isFallback) {
    
    ConnectionSpec specToApply = supportedSpec(sslSocket, isFallback);

    if (specToApply.tlsVersions != null) {
    
      sslSocket.setEnabledProtocols(specToApply.tlsVersions);
    }
    if (specToApply.cipherSuites != null) {
    
      sslSocket.setEnabledCipherSuites(specToApply.cipherSuites);
    }
  }

也就是说,最终sslSocket实例中的enabledProtocols,除了基于TlsCompatSocketFactory中对sslSocket设置的enabledProtocols外,最终还会和ConnectionSpec内部的tlsVersions取交集后,再次赋值给sslSocket实例中的enabledProtocols



三、结语

总体上来说,Https通信时,SSL/TLS的协议版本,在客户端,首先取决于Android系统默认支持下的协议版本,并与ConnectionSpec内部的tlsVersions取交集,在服务端则依赖于服务端的配置。在握手阶段,客户端会和服务端协商最终的协议版本,取用两者同时支持的最高版本。

一般情况下,终端可以尽量放宽协议版本,这样当服务端更改协议版本,甚至只支持某一个协议版本(如TLSv1.1)时,在协议版本协商阶段,都是可以有尽量匹配的版本,从而对Https通信不造成影响。当然,Android 5.0以上的系统中,默认情况下,客户端对主流的协议版本都是支持的,一般不用做特殊处理。

end~

转载于:

https://juejin.im/post/5df8c7006fb9a01606716ba9

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/speverriver/article/details/107410847

智能推荐

深度|详解自动驾驶核心部件激光雷达,它凭什么卖70万美元?_weixin_34095889的博客-程序员ITS203

本月初,全球传感器与智能化发展高峰论坛在北京亦庄召开,会议探讨了当前智能传感器发展现状及技术瓶颈,以便实现传感器技术未来发展的弯道超车。在无人驾驶分论坛上与会专家探讨了激光雷达的重要作用,笔者也分享了自己的一些体会并介绍了2016年未来挑战赛中的针对性的内容设置和参赛无人车队技术进展情况等。得出总结是,激光雷达在现阶段智能车辆实现中是不可或缺的传感器...

第四章 数学规划模型_小问号^_^的博客-程序员ITS203_规划模型

第四章 数学规划模型在高中就已经初步了解过线性规划,在对决策变量的取值范围有范围限制条件下求目标值。实际问题中通常有多个决策变量,可行域复杂;目标函数也是多元函数,模型通常表示为min(max)&nbsp;z=f(x)s.t.&nbsp;gi(x)≤0(也可以为等式),i=1,2,⋅⋅⋅⋅mmin(max)\ z=f(x)\\s.t.\ g_i(x)≤0(也可以为等式),i=1,2,····mmin(max)&nbsp;z=f(x)s.t.&nbsp;gi​(x)≤0(也可以为等式),i=1,2

BZOJ-1085 SCOI 2005 骑士精神 迭代加深 A*爆搜_Lazer2001的博客-程序员ITS203

大家都很强, 可与之共勉 。1085: [SCOI2005]骑士精神 Time Limit: 10 Sec Memory Limit: 162 MB Description  在一个5×5的棋盘上有12个白色的骑士和12个黑色的骑士, 且有一个空位。在任何时候一个骑士都能按照骑 士的走法(它可以走到和它横坐标相差为1,纵坐标相差为2或者横坐标相差为2,纵坐标相差为1的格子)移动到空 位上。

虚拟机服务器渗透,对一台虚拟主机服务器的渗透 -电脑资料_ElemeFe的博客-程序员ITS203

前几天听一树说了惊云下载系统的漏洞问题文件出在admin/user.asp提交http://www.xxx.com/down/admin/user.asp?user=admin’ and asc(mid(pwd,1,1))&gt;37 and ’1’=’1就相当于:select * from UserInfo where user=’admin’ and asc(mid(pwd,1,1))&gt;...

Oracle RAC集群重启_蚂蚁搬家~的博客-程序员ITS203_rac集群重启

SQL&gt; show parameter nameNAME TYPE VALUEcell_offloadgroup_name stringdb_file_name_convert stringdb_name ...

android四大组件之总结篇,Android 四大组件之Activity 基础总结(1)_weixin_39941732的博客-程序员ITS203

Activity 是我们在学习android 的时候最先接触到的东西,也是android 开发过程中不可少的组件。而 在我们android 学习中,对activity 有个全面的认识是很重要的。本人在学习android 以来,对activity 也是又爱又恨,所以特意做了个总结,希望能对 那些 activity 认识还不够的“同鞋”一些帮助。内容提要1、Activity 的概念2、Activity...

随便推点

【WLAN】【测试】Linux下aircrack-ng的应用之空口抓包全解_花神庙码农的博客-程序员ITS203_linux空口抓包

简介aircrack-ng是一套完整的访问wifi网络安全的套件,具体包含:airmon-ngairodump-ngaireplay-ngaircrack-ng说明说明参考资料http://www.aircrack-ng.org/

ORA-00119和ORA-00132的解决方案_Has_it的博客-程序员ITS203_ora00119

在学习tnsname.ora listener.ora sqlnet.ora的过程中,感觉tnsname.ora 里面的LISTEN_ORCL没有作用,直接删除了。导致重新启动数据库时报ORA-00119: invalid specification for system parameter LOCAL_LISTENERORA-00132: syntax error or unresolv

Jupyter notebook 批量复制几个cell_caesarhtx的博客-程序员ITS203_jupyter如何选中多个cell

使用Jupyter的时候某些情况下需要搬运几个cell过去#-----更新-----其实可以在用shift连续选中- -我绕路了挨个复制连续的十来个过分费时费力如何效率较高地处理这件事呢Step 1.找到准备复制的 源文件 的地址 在电脑中用通常的记事本打开,sublime或者notepad++之类这是展现在jupyter里面的样子这是从文本编辑器打开的样子...

Python基础-4列表、元组_迷路的小绅士的博客-程序员ITS203

一、列表的格式# [数据1, 数据2, 数据3, 数据4......]# 列表的作用是一次性存储多个数据,程序员可以对这些数据进行的操作有:增、删、改、查二、查找1.下标name_list = ['Tom','Lily','Rose']print(name_list[1])print(name_list[0])print(name_list[2])2.函数index(...

艾提拉近年来技术大总结_attilax的博客-程序员ITS203

##基本信息 姓名:艾提拉民族:汉生日:1984.3 email:[email protected] 英文名&amp;网名id : attilax 曾用名:艾龙 ##主要方向与目标: 技术方向 Cto cio 技术总监技术经理 软件讲师,培训师,架构师,高级工程师,咨询 顾问 项目经理 it支持 ...

推荐文章

热门文章

相关标签