• 车载双向认证框架设计


    最近工作需要,手写了一个双向认证库,可以用在Java、Android上,不限于PC/手机、车载平台。首先我们来看看双向认证的原理机框架设计思路,最后会给出下载链接大家可以体验或者源码参考。
    因为可以和FlexNet网络库(参考我写的网络库:文章链接)搭配使用,所以这里我也称之为Flex双向认证。

    原理

    Flex双向认证是基于标准的HTTPS进行的安全认证。在说 双向认证 之前,我们需要了解标准的HTTPS;要了解HTTPS我们先要了解 HTTP,因为 HTTPHTTPS通讯的基础。

    HTTP(HyperText Transport Protocol)超文本传输协议,它用于传输客户端和服务器端的数据。

    HTTP标准交互流程

    不难看出HTTP使用很简单也很方便,但却存在以下 3 个致命问题:

    • 使用明文通讯,内容可以被窃听。
    • 不验证通讯方的真实身份,可能会遭到伪装。
    • 无法证明报文的完整性,很容易被篡改。

    HTTPS通信

    HTTPS是针对以上问题在HTTP 协议的基础上添加了加密机制 的通信方案。根据需求,主要分为单向认证双向认证

    单向认证

    单向认证的过程,客户端对服务器端下发的服务器端公钥证书进行验证,然后建立安全通信通道。

    双向认证

    双向通信流程,客户端除了需要对服务器端下发的服务器公钥证书进行验证外,还需要把客户端的公钥证书上传到服务器端给服务器端进行验证。等双方都认证通过了,才开始建立安全通信通道进行数据传输。

    Flex双向认证

    Flex库选取了安全性更高的双向认证方案。并进一步对证书安全性进行了处理。

    资源配置

    APP需要从车机端获取到对应的证书,才能进行双向认证下一步的逻辑。

    统一的SSLLib

    为了避免各个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
    
            )
    
        
    
        }
    

    Q&A

    对出现问题时的排查和原因的归纳,如需要看出Lib库的日志,请配置SSLLogUtils#init

    CODE码对照表
    CODE码(详见SSLConst类)值/int意义
    CODE_SUC0构建成功
    CODE_DUPLICATION1~~重复获取 ~~在v1.3.0以及之后,此错误码不会再存在
    CODE_OVERTIME2获取超时
    CODE_PKI_ERROR3获取PKI资源异常,一般是rom有问题
    CODE_CERTS_ERROR4获取证书异常,一般是证书不存在;也可能是由于获取证书时user unlocked;还可能是PKIAutoInstall没有检验到你应用的包名,已检测证书存在且已和rom申请白名单或system权限,一般是rom的PKIAutoInstall没有检验到你应用的包名,需要使用push~~推送应用并重启设备 ~~,使用install安装的应用,重启车机触发ROM的权限和包名校验,即可获取成功
    CODE_THREAD_ERROR5HttpsUtils.buildSSL调用的线程不是主线程在v1.5.0以及之后,此错误码不会再存在
    CODE_CONTEXT_ERROR6当通过context获取应用的application失败时,会回调此错误码

    出现以上错误码,见问题排查

    问题排查

    HttpsUtils.buildSSL返回null,排查问题,确认台架或车辆是否包含证书的步骤。

    1. 是否系统应用,aar版本( <=v1.1.0)仅支持系统应用;如果是系统应用的方式获取,无法确定自己的应用是否是系统应用,需要和@秦金川 确认,申请系统应用权限也是和其协商。

    image.png

    1. 车机或者台架证书是否刷入成功;

    1. 获取证书要在user unlock之后
    2. 白名单方案获取证书,一定要确认自己的应用已经申请了白名单,可以向@裴峥 进行确认(如需要新增也是向其申请); 且验证的rom >=W48D6

    1. 应用是否在当前ROM的白名单中,可以在第一次刷入证书之后,收集日志查看 PKIAutoInstall关键字TAG,看是否有自己的包名

    oktb时已加入白名单的应用清单

    
    
     
    
         
    
             com.here.hnod.elise.elise_navigation
    
             com.abupdate.ota
    
             com.example.manual
    
         
    
     
    
    1. 是否在应用的Manifest文件中添加了权限,只有是JAR接入才需要自己配置
    
    

    如果按照以上步骤排查之后,还无法正常获取证书,查看 ISSUE

    常见问题
    • 引入AAR或Maven依赖后,提示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的版本

    • 双向认证失败,返回错误获取证书为null

    一般是证书刷入失败,如果确认证书刷入成功,确认以下三点:

    1. master和slave版本需要一致

    1. 证书一定要刷在master上

    2. rom和证书需要都是CN或EU

    • 引入双向认证的aar以后,使用车机调试,报javax.netssl.SSLPeerUnverifiedException: Hostname xxx not verifie错误

    需要设置clientBuilder.hostnameVerifier 验证

    • 测试的RESTful API返回错误

    服务器返回的内部错误,预示双向认证已经完成,是服务器接口的内部逻辑问题,询问对应的服务器开发人员即可。

    • SDK正常获取证书之后,请求还是报握手错误

    参考通过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
    
     )
    
    对应网络库设置双向认证的配置

    存在不同的网络协议,目前暂时给出一些常用的协议的用法

    OKHttp

    MQTT

    WebSocket

    排查问题可以开启日志,需要配置SSLLogUtils#init

    
    
     /**
    
     * 在应用的application中配置即可;需要在获取双向认证的配置前调用
    
     *  @param ctx 获取集成的应用的包名
    
     *  @param enable 是否在控制台输出日式
    
     *  @param logAdapter 提供给开发者去使用自定义的日志工具,默认直接使用[DefaultLogAdapter]
    
     * */
    
    fun init(
    
        ctx: Context,
    
        enable: Boolean = BuildConfig.DEBUG,
    
        logAdapter: ILogAdapter? = DefaultLogAdapter()
    
    ) 
    
    简易Demo

    使用此lib的简单demo

    暂时无法在文档外展示此内容

    • demo提供SSLSocketUtils展示重试获取双向认证配置逻辑
    • demo提供LiveDataEventBus监听双向认证配置获取结果,进行下一步网络请求的逻辑
    • demo提供HostnameVerifierUtils用来对证书中的Host进行严格校验
    • demo展示如何使用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修复或小版本(不影响接入)更新修改最后(左起)的版本号;新增方法或参数,修改第二位;第一位版本号修改,只会在方案有大变更的情况才会出现

    V1.6.1
    • 统一白名单和系统应用获取证书的逻辑
    • 更新demo的配置信息
    Jar
    • 新增直接提供jar包,需要开发者在manifest中自行配置对应的权限和配置
    
    
    
    
    • Lib中使用的依赖需要开发者自行导入
    implementation 'androidx.annotation:annotation:1.5.0'
    

    暂时无法在文档外展示此内容

    AAR

    暂时无法在文档外展示此内容

    V1.6.0

    暂时无法在文档外展示此内容

    • 新增buildSSLAtMaster支持在master soc上获取双向认证证书(目前方案仅支持系统应用和cn/eu车机)
    • 支持自定义日志打印类,lib默认使用Android默认日志工具类实现日志打印
    
    
     /**
    
     *
    
     * 开发者可以实现此接口后,在[com.max.ssl.SSLLogUtils]设置此对象,来使用自定义的日志实现
    
     */
    
    interface ILogAdapter {
    
        fun log(@LogLevel logLevel: Int, tag: String, content: String)
    
    }
    
    • demo新增buildMasterSSL方法测试通过master soc获取证书
    V1.5.0.1

    暂时无法在文档外展示此内容

    • 增加对低版本ROM的兼容,确保rom的情况也能正常获取证书
    • 移除匹配证书异常的兜底方案,直接返回获取失败
    V1.5.0.0

    > 注意: rom >=W14D4(非RC04版本,需要使用主线版本,如:20.32.20.000001.231661)

    暂时无法在文档外展示此内容

    • 增加对获取证书时的时机的处理,防止获取证书过早导致获取失败
    • 增加校验PKI的前置准备是否完成的逻辑
    • 修改日志库为统一工具库,直接引入aar的开发者需要引入timber
    implementation 'com.jakewharton.timber:timber:5.0.1'//版本可以自行指定
    
    V1.4.0.2

    暂时无法在文档外展示此内容

    • 增加对获取证书时的异常处理
    V1.4.0.1

    暂时无法在文档外展示此内容

    • 修改混淆配置,防止和其他lib库冲突
    V1.4.0.0

    暂时无法在文档外展示此内容

    • 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失败时,会回调此错误码
    • 优化lib结构,将单个构建作为task的一个request,以便于后续扩展
    • 版本号由三位变更为四位,后续Bug修复更新修改最后(左起)的版本号;小版本(不影响接入)修改第三位数;新增方法或参数,修改第二位;第一位版本号修改,只会在方案有大变更的情况才会出现
    V1.3.0

    暂时无法在文档外展示此内容

    • 修改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无法控制调用时机的场景。
    • 移除本地资源配置支持
    • demo提供SSLSocketUtils展示重试获取双向认证配置逻辑
    • demo提供LiveDataEventBus监听双向认证配置获取结果,进行下一步网络请求的逻辑
    • HttpsUtils.buildSSL,修改为仅支持主线程调用
    • 新增错误码SSLConst.CODE_THREAD_ERROR,当HttpsUtils.buildSSL方法在非主线程调用时,会回调此错误码
    V1.2.1

    暂时无法在文档外展示此内容

    • callback新增错误码回调
    • 将老的callback标记为过时的,待后续版本更新将进行废除
    
    
     /**
    
     * 构建路特斯的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
    
    ) 
    
    V1.2.0

    暂时无法在文档外展示此内容

    • 新增白名单方案
    
    
     /**
    
     * 构建路特斯的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
    
    ) 
    
    • Demo新增HostnameVerifierUtils用来对证书中的Host进行严格校验
    • 过滤短时间多次调用获取证书方法导致的问题
    • 冗余重载HttpsUtils.buildSSL,以供java端使用时不用填充各个参数
    V1.1.0

    暂时无法在文档外展示此内容

    • 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线程
    • 统一lib日志打印工具为SSLLogUtilsSSLLogUtils.isLogEnable由开发者配置是否打印lib中的日志
    • proguard-rules配置增加保留包名,以防止与开发者的其他依赖混淆冲突
    • 支持本地资源配置
    V1.0.0

    第一个正式发布的release版本,提供双向认证和自定义双向认证方案。

    Maven依赖

    首先在项目的根目录下的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
    
    ISSUE

    首先先按照文档中 Q&A 进行证书问题排查,确认不是车机证书不存在导致的问题;其次按照 常见问题 进行排查,确认不属于已有问题。

    最后 提供车机或台架系统版本信息、集成的应用的manifest文件截图车机或台架查询证书的命令后的执行结果截图,以及开启 lib库的日志(SSLLogUtils中存在开关控制)提供完整日志以供分析。

    需要aar包或者源码的朋友,可以在评论区留下联系方式,我会第一时间发给你

  • 相关阅读:
    5G高算力智能模组:引领AIoT进入摩尔定律时代
    Windows 下 bat 脚本调用 Git bash 环境 sh 脚本
    【SSM】Myeclipse & idea 的版 ssm框架——整合过程
    Redis 高可用之持久化
    web安全之XSS攻击
    策略梯度玩 cartpole 游戏,强化学习代替PID算法控制平衡杆
    【Spring】Spring Security学习笔记
    在uni-app中引入uView
    【深度学习】YOLOv5 工程落地部署过程 MNN
    如何使用 Delphi/Lazarus 代码在 FastReport VCL 中生成二维码?
  • 原文地址:https://blog.csdn.net/MC_hust/article/details/139996376