最近工作需要,手写了一个双向认证库,可以用在Java、Android上,不限于PC/手机、车载平台。首先我们来看看双向认证的原理机框架设计思路,最后会给出下载链接大家可以体验或者源码参考。
因为可以和FlexNet网络库(参考我写的网络库:文章链接)搭配使用,所以这里我也称之为Flex双向认证。
Flex双向认证
是基于标准的HTTPS
进行的安全认证。在说 双向认证
之前,我们需要了解标准的HTTPS
;要了解HTTPS
我们先要了解 HTTP
,因为 HTTP
是 HTTPS
通讯的基础。
HTTP(HyperText Transport Protocol)超文本传输协议,它用于传输客户端和服务器端的数据。
HTTP标准交互流程
不难看出HTTP
使用很简单也很方便,但却存在以下 3 个致命问题:
HTTPS
是针对以上问题在HTTP
协议的基础上添加了加密机制 的通信方案。根据需求,主要分为单向认证
和双向认证
。
单向认证的过程,客户端对服务器端下发的服务器端公钥证书进行验证,然后建立安全通信通道。
双向通信流程,客户端除了需要对服务器端下发的服务器公钥证书进行验证外,还需要把客户端的公钥证书上传到服务器端给服务器端进行验证。等双方都认证通过了,才开始建立安全通信通道进行数据传输。
Flex库
选取了安全性更高的双向认证
方案。并进一步对证书安全性进行了处理。
APP需要从车机端获取到对应的证书,才能进行双向认证下一步的逻辑。
为了避免各个APP都需要单独去开发一套双向认证的方案,现实现了一套Flex双向认证
工具库。
构建双向认证需要的对象HttpsUtils.buildSSL
,此方法是异步回调,一定要等到回调才能进行双向认证的后续逻辑。
/**
* 构建ssl认证
* 如果是在Application中调用,需要注意配置在主进程中使用,防止多进程导致异常
*
* @param ctx 获取车机端内置资源需要使用context
* @param callback 回调双向认证所需要的资源,为null表示构建失败
* */
private fun buildCarSSL() {
HttpsUtils.buildSSL(this) {
mCarParams = it
Log.e("SSL", "buildCarSSL:${it == null}")
}
}
在OKHTTP中配置双向认证
/**
* 创建默认的OkHttpClient
* 和服务器协 信息
* */
private fun buildOkHttpClient(): OkHttpClient {
val clientBuilder = OkHttpClient.Builder()
// 构建的双向认证对象 SSLParams
if (MainActivity. mCarParams != null) {
val ssl = MainActivity. mCarParams !!
clientBuilder.sslSocketFactory(ssl.sslSocketFactory, ssl.trustManager)
}
//不校验host;如果需要检验host,可以针对自己需要调用的服务器进行hostname比对
clientBuilder.hostnameVerifier { hostname, session -> true }
mInterceptors.forEach {
clientBuilder.addInterceptor(it)
}
if (BuildConfig.DEBUG) {//打印调试日志
val httpLoggingInterceptor = HttpLoggingInterceptor()
httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
clientBuilder.addInterceptor(httpLoggingInterceptor)
}
clientBuilder.connectTimeout(OkHttpConst.CONNECT_TIME, TimeUnit.SECONDS)
clientBuilder.readTimeout(OkHttpConst.READ_TIME, TimeUnit.SECONDS)
return clientBuilder.build()
}
构建双向认证需要的对象SSLUtils.getDoubleSslFactory
。
/**
* TODO: demo调试的证书配置,对应的服务器是demo的测试云端;如果需要自己进行测试,需要使用自己的云端配置和修改
* @see LocalSSLApiStore.baseUrl
* */
private fun buildLocalSSL() {
val caIs = assets.open("ca-cert.pem")
val clientPfxIs = assets.open("client.p12")
val clientPsd = "hbzq253503125"
// mSSLParams = SSLUtils.getSingleSslFactory(
// arrayOf(caIs)
// )
//双向认证
mSSLParams = SSLUtils.getDoubleSslFactory(
arrayOf(caIs),
clientPfxIs, clientPsd
)
}
对出现问题时的排查和原因的归纳,如需要看出Lib库的日志,请配置SSLLogUtils#init
。
CODE码(详见SSLConst 类) | 值/int | 意义 |
---|---|---|
CODE_SUC | 0 | 构建成功 |
1 | ~~重复获取 ~~在v1.3.0以及之后,此错误码不会再存在 | |
CODE_OVERTIME | 2 | 获取超时 |
CODE_PKI_ERROR | 3 | 获取PKI资源异常,一般是rom有问题 |
CODE_CERTS_ERROR | 4 | 获取证书异常,一般是证书不存在;也可能是由于获取证书时user unlocked;还可能是PKIAutoInstall没有检验到你应用的包名,已检测证书存在且已和rom申请白名单或system权限,一般是rom的PKIAutoInstall没有检验到你应用的包名, |
CODE_THREAD_ERROR | 5 | HttpsUtils.buildSSL |
CODE_CONTEXT_ERROR | 6 | 当通过context获取应用的application失败时,会回调此错误码 |
出现以上错误码,见问题排查。
HttpsUtils.buildSSL
返回null,排查问题,确认台架或车辆是否包含证书的步骤。
PKIAutoInstall
关键字TAG,看是否有自己的包名oktb时已加入白名单的应用清单
- com.here.hnod.elise.elise_navigation
- com.abupdate.ota
- com.example.manual
如果按照以上步骤排查之后,还无法正常获取证书,查看
ISSUE
Module was compiled with an incompatible version of Kotlin
这个是由于lib开发的kotlin版本为1.7.10,项目引入的
id 'org.jetbrains.kotlin.android' version '1.6.20'
版本低于1.7.10就会导致lint检测无法通过,需要升级kotlin版本。
如果不想进行升级,可以配置lint忽略检测:(不推荐使用此方法)
lintOptions {
checkReleaseBuilds false
abortOnError false
}
HttpsUtils.buildSSL
提示权限相关问题确认调试的应用是否进行了系统签名,一般是签名校验不通过导致权限无法获取
Chain validation failed
或检验服务器证书不对首先确认服务器是否配置了双向认证的对应证书,其次查看日志中的台架或实车时间是否正确。
此问题在服务器配置正确的情况,都是由于车机或台架的时间异常导致
确认使用的台架或者实车 是否已经刷入了对应证书;且rom****需要是>=2022/9/15的版本
一般是证书刷入失败,如果确认证书刷入成功,确认以下三点:
master和slave版本需要一致
证书一定要刷在master上
rom和证书需要都是CN或EU
javax.netssl.SSLPeerUnverifiedException: Hostname xxx not verifie
错误需要设置
clientBuilder.hostnameVerifier
验证
RESTful API
返回错误服务器返回的内部错误,预示双向认证已经完成,是服务器接口的内部逻辑问题,询问对应的服务器开发人员即可。
参考通过Wireshark分析TLS协议 抓包确认云端和车机证书是否环境一致
集成后实现功能仅需 两步:
获取证书
/**
* 构建路特斯的ssl认证
* 如果是在Application中调用,需要注意配置在主进程中使用,防止多进程导致异常
*
* @param ctx 获取车机端内置资源需要使用context
* @param isByWhiteList 是否通过白名单模式获取证书
* @param overtime 配置超时时间,为 SSLMills.MILLS_MIN表示没有超时直到回调成功或失败;
* 其它限定在SSLMills的时间,表示此方案需要在指定时间完成操作,否证就会在到达指定时间回调null和指定错误码
* @param listener 回调监听结果 [SSLConst.CODE_SUC] 和 [SSLParams]
* */
fun buildSSL(
ctx: Context,
isByWhiteList: Boolean = false,
@SSLMills overtime: Long = SSLMills.MILLS_MIN,
listener: SSLListener
)
存在不同的网络协议,目前暂时给出一些常用的协议的用法
排查问题可以开启日志,需要配置
SSLLogUtils#init
/**
* 在应用的application中配置即可;需要在获取双向认证的配置前调用
* @param ctx 获取集成的应用的包名
* @param enable 是否在控制台输出日式
* @param logAdapter 提供给开发者去使用自定义的日志工具,默认直接使用[DefaultLogAdapter]
* */
fun init(
ctx: Context,
enable: Boolean = BuildConfig.DEBUG,
logAdapter: ILogAdapter? = DefaultLogAdapter()
)
使用此lib的简单demo
暂时无法在文档外展示此内容
SSLSocketUtils
展示重试获取双向认证配置逻辑LiveData
、EventBus
监听双向认证配置获取结果,进行下一步网络请求的逻辑HostnameVerifierUtils
用来对证书中的Host进行严格校验SSLLogUtils
展示调试日志以供开发者集成后,以AAR形式提供给第三方使用
-keep class com.max.ssl.SSLUtils {*;}
-keep class com.max.ssl.SSLMills {*;}
-keep class com.max.ssl.SSLConst {*;}
每个release版本的更新log,时间倒叙。
版本发布规则
四位版本号(v1.0.0.0),Bug修复更新修改最后(左起)的版本号;小版本(不影响接入)修改第三位数;新增方法或参数,修改第二位;第一位版本号修改,只会在方案有大变更的情况才会出现
三位版本号(v1.0.0.0),Bug修复或小版本(不影响接入)更新修改最后(左起)的版本号;新增方法或参数,修改第二位;第一位版本号修改,只会在方案有大变更的情况才会出现
manifest
中自行配置对应的权限和配置
implementation 'androidx.annotation:annotation:1.5.0'
暂时无法在文档外展示此内容
暂时无法在文档外展示此内容
暂时无法在文档外展示此内容
/**
*
* 开发者可以实现此接口后,在[com.max.ssl.SSLLogUtils]设置此对象,来使用自定义的日志实现
*/
interface ILogAdapter {
fun log(@LogLevel logLevel: Int, tag: String, content: String)
}
buildMasterSSL
方法测试通过master soc
获取证书暂时无法在文档外展示此内容
> 注意: rom >=W14D4(非RC04版本,需要使用主线版本,如:20.32.20.000001.231661)
暂时无法在文档外展示此内容
timber
implementation 'com.jakewharton.timber:timber:5.0.1'//版本可以自行指定
暂时无法在文档外展示此内容
暂时无法在文档外展示此内容
暂时无法在文档外展示此内容
SSLLogUtils
增加init方法,以获取集成的应用包名,便于后续调试
/**
* 在应用的application中配置即可;需要在获取双向认证的配置前调用
* @param ctx 获取集成的应用的包名
* @param enable 是否在控制台输出日式
* */
fun init(ctx: Context, enable: Boolean = BuildConfig.DEBUG) {
}
HttpsUtils.buildSSL
新增overtime
参数
/**
* 构建路特斯的ssl认证
* 如果是在Application中调用,需要注意配置在主进程中使用,防止多进程导致异常
*
* @param ctx 获取车机端内置资源需要使用context
* @param isByWhiteList 是否通过白名单模式获取证书
* @param overtime 配置超时时间,为 SSLMills.MILLS_MIN表示没有超时直到回调成功或失败;
* 其它限定在SSLMills的时间,表示此方案需要在指定时间完成操作,否证就会在到达指定时间回调null和指定错误码
* @param listener 回调监听结果 [SSLConst.CODE_SUC] 和 [SSLParams]
* */
fun buildSSL(
ctx: Context,
isByWhiteList: Boolean = false,
@SSLMills overtime: Long = SSLMills.MILLS_MIN,
listener: SSLListener
)
SSLConst.CODE_CONTEXT_ERROR
,当通过context获取应用的application失败时,会回调此错误码暂时无法在文档外展示此内容
HttpsUtils.buildSSL
的方法体回调为SSLListener
接口回调,让java端不需要引入kotlin的依赖也能集成//TODO: 白名单方式或者system方案配置按照需求传递;注意一个app不支持同时获取白名单和系统方案的双向认证配置,会以前一次构建成功的为准
HttpsUtils.buildSSL(ctx, true, listener = object : SSLListener {
override fun callback(code: Int, ssl: SSLParams?) {
SSLLogUtils.d(TAG, "buildSSL--code:$code;ssl:$ssl")
}
})
HttpsUtils.buildSSL
的构建规则,支持同时多次获取同一个方案的配置,即app只能获取白名单或system方案的双向认证配置;用以支持单个宿主或aar,有多个插件或aar需要实现双向认证功能,但宿主或aar无法控制调用时机的场景。SSLSocketUtils
展示重试获取双向认证配置逻辑LiveData
、EventBus
监听双向认证配置获取结果,进行下一步网络请求的逻辑HttpsUtils.buildSSL
,修改为仅支持主线程调用SSLConst.CODE_THREAD_ERROR
,当HttpsUtils.buildSSL
方法在非主线程调用时,会回调此错误码暂时无法在文档外展示此内容
/**
* 构建路特斯的ssl认证
* 如果是在Application中调用,需要注意配置在主进程中使用,防止多进程导致异常
*
* @param ctx 获取车机端内置资源需要使用context
* @param overTime 配置超时时间,为 SSLMills.MILLS_MIN表示没有超时直到回调成功或失败;
* 其它限定在SSLMills的时间,表示此方案需要在指定时间完成操作,否证就会在到达指定时间回调null,并停止执行
* @param isByWhiteList 是否通过白名单模式获取证书
* @param callbackDeprecated 回调双向认证所需要的资源,为null表示构建失败,在v1.3.0之后已被callback替代
* @param code 错误码
* @see SSLConst.CODE_SUC
* @param ssl 双向认证所需要的资源,为null表示构建失败
* */
private fun buildSSL(
ctx: Context,
@SSLMills overTime: Long = SSLMills.MILLS_MIN,
isByWhiteList: Boolean = false,
callbackDeprecated: ((
ssl: SSLParams?
) -> Unit)? = null,
callback: ((
code: Int,
ssl: SSLParams?
) -> Unit)? = null
)
暂时无法在文档外展示此内容
/**
* 构建路特斯的ssl认证
* 如果是在Application中调用,需要注意配置在主进程中使用,防止多进程导致异常
*
* @param ctx 获取车机端内置资源需要使用context
* @param overTime 配置超时时间,为 SSLMills.MILLS_MIN表示没有超时直到回调成功或失败;
* 其它限定在SSLMills的时间,表示此方案需要在指定时间完成操作,否证就会在到达指定时间回调null,并停止执行
* @param isByWhiteList 是否通过白名单模式获取证书
* @param callback 回调双向认证所需要的资源,为null表示构建失败
* */
fun buildSSL(
ctx: Context,
@SSLMills overTime: Long = SSLMills.MILLS_MIN,
isByWhiteList: Boolean = false,
callback: (
ssl: SSLParams?
) -> Unit
)
HostnameVerifierUtils
用来对证书中的Host进行严格校验HttpsUtils.buildSSL
,以供java端使用时不用填充各个参数暂时无法在文档外展示此内容
HttpsUtils.buildSSL
新增超时时间配置
/**
* 构建路特斯的ssl认证
* 如果是在Application中调用,需要注意配置在主进程中使用,防止多进程导致异常
*
* @param ctx 获取车机端内置资源需要使用context
* @param overTime 配置超时时间,为 SSLMills.MILLS_MIN表示没有超时直到回调成功或失败;
* 其它限定在SSLMills的时间,表示此方案需要在指定时间完成操作,否证就会在到达指定时间回调null,并停止执行
* @param callback 回调双向认证所需要的资源,为null表示构建失败
* */
fun buildSSL(
ctx: Context,
@SSLMills overTime: Long = SSLMills.MILLS_MIN,
callback: (ssl: SSLParams?) -> Unit
)
HttpsUtils.buildSSL
修改callback回调线程,指定为UI线程SSLLogUtils
,SSLLogUtils.isLogEnable
由开发者配置是否打印lib中的日志第一个正式发布的release版本,提供双向认证和自定义双向认证方案。
首先在项目的根目录下的
build.gradle
中配置
maven {
url "https://nexus-cockpit.lotuscars.com.cn/repository/maven-releases/"
}
maven {//不使用snapshot版本的情况,不需要引入
url "https://nexus-cockpit.lotuscars.com.cn/repository/maven-snapshots/"
}
其次在需要使用的module的
bulid.gradle
配置
implementation "com.lotus.ssl:ssllib:$sslVersion"//配置指定的版本号即可,如1.6.0
首先先按照文档中 Q&A
进行证书问题排查,确认不是车机证书不存在导致的问题;其次按照 常见问题
进行排查,确认不属于已有问题。
最后 提供车机或台架系统版本信息
、集成的应用的manifest文件截图
、车机或台架查询证书的命令
后的执行结果截图,以及开启 lib库
的日志(SSLLogUtils
中存在开关控制)提供完整日志以供分析。
需要aar包或者源码的朋友,可以在评论区留下联系方式,我会第一时间发给你