flutter dio Https证书校验和certificate_pinning源码解析

http回顾

http 协议 是应用层协议 同时还有 ftp dns tftp smtp snmp ,是一个基于TCP/IP通信协议来传递数据.

传输的类型由Content-Type加以标记,同时具有无连接、无状态等特点

一个请求由请求行(request line)、请求头部(header)、空行和请求数据四个部分组成

现在常用的是http1 的协议。 而http2 的协议 是在1的基础上升级,增加了安全性是机遇https,增加了效率通过二进制分帧来进行数据传输

在http1.1中,客户端在同一时间,针对同一域名下的请求有一定数量的限制

而http2.0中的多路复用优化了这一性能。多路复用允许同时通过单一的http/2 连接发起多重的请求-响应消息http2 不再依赖多个TCP连接去实现多流并行了

http 2.0 连接都是持久化的,接可以承载数十或数百个流的复用,

而且使用encoder来减少需要传输的header大小的同时通讯双方各自缓存一份头部字段表,避免了重复header的传输

tcp 回顾

tcp 位于传输层

用来传输数据,常见的三次握手

ip回顾

用于标识网络中的唯一一台主机或路由器. 一般用DNS 协议提供通过域名查找 IP 地址

https 回顾

一般理解为HTTP+SSL/TLS,通过 SSL证书来验证服务器的身份,并为浏览器和服务器之间的通信进行加密。

SSL/TLS是一个安全通信框架,上面可以承载HTTP协议或者SMTP/POP3协议等。

https请求流程

1.首先客户端通过URL访问服务器建立SSL连接。
2.服务端收到客户端请求后,会将网站支持的证书信息(证书中包含公钥)传送一份给客户端。
3.客户端的服务器开始协商SSL连接的安全等级,也就是信息加密的等级。
4.客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。
5.服务器利用自己的私钥解密出会话密钥。
6.服务器利用会话密钥加密与客户端之间的通信。

回顾得知, https 会先建立ssl 连接, 请求证书,  如果证书不正确,就终止步骤,

证书的检查由系统操作,dio 没有api 可以自定义检查 ,只有一个不可信证书处理机制

用来配处理自己的证书(注意只有不可信的证书才会执行)

(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate  = (client) {
  client.badCertificateCallback=(X509Certificate cert, String host, int port){
    if(cert.pem==PEM){ // Verify the certificate
      return true;
    }
    return false;
  };
};

或者添加自签名证书(其他可信任的依然可以通过)

(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate  = (client) {
  SecurityContext sc = SecurityContext();
  //file is the path of certificate
  sc.setTrustedCertificates(file);
  HttpClient httpClient = HttpClient(context: sc);
  return httpClient;
};

如果想固定的证书校验通过. 

在dio 里面有一个http_certificate_pinning 插件, 用来配置固定证书

  var dio =  Dio(BaseOptions(baseUrl: baseUrl))
        ..interceptors.add(CertificatePinningInterceptor(allowedSHAFingerprints));
源码查看
CertificatePinningInterceptor
@override
Future onRequest(
    RequestOptions options,
    RequestInterceptorHandler handler,
    ) async {
  try {
    //已验证的跳过
    if (verifiedURLs.contains(options.baseUrl)) {
      return super.onRequest(options, handler);
    }
    //iOS问题
    if (Platform.isIOS && secure != null) {
      await secure;
    }
    //当前插件是基于 ssl_pinning_plugin 插件二次开发
    //直接看android 实现部分
    secure = HttpCertificatePinning.check(
      serverURL: options.baseUrl,
      headerHttp: options.headers.map((a, b) => MapEntry(a, b.toString())),
      sha: SHA.SHA256,
      allowedSHAFingerprints: _allowedSHAFingerprints,
      timeout: _timeout,
    );

    secure?.whenComplete(() => secure = null);
    final secureString = await secure ?? '';

    if (secureString.contains('CONNECTION_SECURE')) {
      verifiedURLs.add(options.baseUrl);
      return super.onRequest(options, handler);
    } else {
      handler.reject(
        DioError(
          requestOptions: options,
          error: CertificateNotVerifiedException(),
        ),
      );
    }
  } on Exception catch (e) {
  ...省略
  }

最终调用了android 的handleCheckEvent 方法

private fun handleCheckEvent(call: MethodCall, result: Result) {
//取值
    val arguments: HashMap<String, Any> = call.arguments as HashMap<String, Any>
    val serverURL: String = arguments.get("url") as String
    val allowedFingerprints: List<String> = arguments.get("fingerprints") as List<String>
    val httpMethod: String = arguments.get("httpMethod") as String
    val httpHeaderArgs: Map<String, String> = arguments.get("headers") as Map<String, String>
    val timeout: Int = arguments.get("timeout") as Int
    val type: String = arguments.get("type") as String
       //开始检查
    val get: Boolean = CompletableFuture.supplyAsync { this.checkConnexion(serverURL, allowedFingerprints, httpHeaderArgs, timeout, type, httpMethod) }.get()
        //返回结果
    if(get) {
        result.success("CONNECTION_SECURE")
    }else {
        result.error("CONNECTION_NOT_SECURE", "Connection is not secure", "Fingerprint doesn't match")
    }

}

      代码比较简单, 直接忽略中间各种调用跳转。最终调用了getFingerprint

private fun getFingerprint(httpsURL: String, connectTimeout: Int, httpHeaderArgs: Map<String, String>, type: String, httpMethod: String): String {
   //调用java7的API 准备连接
    val url = URL(httpsURL)
    val httpClient: HttpsURLConnection = url.openConnection() as HttpsURLConnection

    if (httpMethod == "Head") httpClient.setRequestMethod("HEAD");

    httpHeaderArgs.forEach { key, value -> httpClient.setRequestProperty(key, value) }
    httpClient.connect()
        //获取服务器证书
    val cert: Certificate = httpClient.getServerCertificates()[0] as Certificate
        //断开链接
    httpClient.disconnect()
   //返回
    return this.hashString(type, cert.getEncoded())

}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值