Https与OkHttp的暧昧关系以及那些不清楚的知识点

0.前言

上周出现一个问题,使用集团SDK中的WebView不能打开某个网页了,打印Log javax.net.ssl.SSLPeerUnverifiedException:*** not vertified。这个错误查了一下午,在最终回家的时候找到了答案,记录下来,是个借鉴,由于项目是使用的集团的网络库,代码不能泄露,这里就用okhttp进行替换。java

1.什么是Https

什么是https,这个概念感受已经烂大街了,没什么想说的,但既然标题都给到这了,情绪也到了,好像不说点又有点感受不对,简单说一下编程

1.1为何使用https

先说传统http有什么缺陷:api

(1)数据明文传播,内容易被窃取监听安全

(2)不验证通讯方的身份,遭遇假装。bash

(3)没法证实报文完整性,可能被篡改。(好比中间人攻击,你用的charles就是这样)服务器

上述及时http的缺陷也是https要解决的问题微信

(1)利用混合加密方式(非对称加密用来传递秘钥,对称加密使用传递的秘钥对数据进 行加密),确保数据内容加密。网络

(2)经过第三方权威机构颁发的证书(包括服务器信息,服务端公钥,以及经过第三方数字证书颁发机构使用其私钥对服务器信息,服务器公钥的hash信息摘要进行的数字签名)来保证通讯方的身份以及报文是否被修改。session

因此HTTP+加密+完整性保护+认证=HTTPSapp

1.2 SSL

https的实现其实就是在TCP与HTTP协议层次之间加了一个SSL层。如图

(HTTPS 使用 SSL(Secure Socket Layer) 和 TLS(Transport Layer Security)这两个协议。TSL是以 SSL为原型开发的协议,有时会统一称该协议为 SSL) SSL协议不只能够被http使用其余应用层协议也可使用

2. Android中的SSLScoketFactory、SSLContext、TrustManagr和HostnameVerifier

2.1

根据api文档,先对这几个翻译一下

SSLContext:

此类的实例表明SSL协议实现,该实现充当安全套接字工厂或SSLEngines的工厂。此类由一组可选的密钥和信任管理器以及安全随机字节的源初始化。

SSLScoketFactory:

SSLSocket的工厂,SSLSocket继承自Socket,提供SSL协议和TLS协议的安全套接字通讯。

TrustManager:信任管理器,咱们通常使用它的子接口X509TrustManager,管理X509证书,验证远程安全套接字(x.509标准规定了证书能够包含什么信息,并说明了记录信息的方法)

HostnameVerifier:

如下解释来自阿里编程规约: 在实现的HostnameVerifier子类中,须要使用verify函数效验服务器主机名的合法性,不然会致使恶意程序利用中间人攻击绕过主机名效验。在握手期间,若是URL的主机名和服务器的标识主机名不匹配,则验证机制能够回调此接口实现程序来肯定是否应该容许此链接,若是回调内实现不恰当,默认接受全部域名,则有安全风险。

3. Okhttp与https

众所周知,Okhttp中真正打开网络链接和发送网络数据是在拦截器里作的,直接看ConnectInterceptor,这是OkHttp中内置的拦截器,用来打开网络连接。

@Override public Response intercept(Chain chain) throws IOException {
    ......
     HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
复制代码

无关代码省略, 首先StreamA了location在调用newStream的时候会找到一个健康的Connection,这个就是streamAllocation.connection返回的RealConnection,生成RealConnection对象后会调用它的connect方法

public void connect(int connectTimeout, int readTimeout, int writeTimeout,
      int pingIntervalMillis, boolean connectionRetryEnabled, Call call,
      EventListener eventListener) {
       ....
        connectSocket(connectTimeout, readTimeout, call, eventListener);
        establishProtocol(connectionSpecSelector, pingIntervalMillis, call, 
       ....
  }
复制代码

留下关键代码,首先创建一个tcp的socket链接,由于ssl层进行握手是也是经过tcp链接进行的,因此先创建socket链接,而后再看establishProtocol方法

private void establishProtocol(ConnectionSpecSelector 
 ...
    connectTls(connectionSpecSelector);
...
  }
复制代码

这里会调用connectTls,从名字就能够看出这里是进行SSL握手的地方

private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
    Address address = route.address();
    SSLSocketFactory sslSocketFactory = address.sslSocketFactory();
    boolean success = false;
    SSLSocket sslSocket = null;
    try {
      // Create the wrapper over the connected socket.
      sslSocket = (SSLSocket) sslSocketFactory.createSocket(
          rawSocket, address.url().host(), address.url().port(), true /* autoClose */);

      // Configure the socket's ciphers, TLS versions, and extensions. ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket); if (connectionSpec.supportsTlsExtensions()) { Platform.get().configureTlsExtensions( sslSocket, address.url().host(), address.protocols()); } // Force handshake. This can throw! sslSocket.startHandshake(); // block for session establishment SSLSession sslSocketSession = sslSocket.getSession(); Handshake unverifiedHandshake = Handshake.get(sslSocketSession); // Verify that the socket's certificates are acceptable for the target host.
      if (!address.hostnameVerifier().verify(address.url().host(), sslSocketSession)) {
        X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0);
        throw new SSLPeerUnverifiedException("Hostname " + address.url().host() + " not verified:"
            + "\n certificate: " + CertificatePinner.pin(cert)
            + "\n DN: " + cert.getSubjectDN().getName()
            + "\n subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
      }

      // Check that the certificate pinner is satisfied by the certificates presented.
      address.certificatePinner().check(address.url().host(),
          unverifiedHandshake.peerCertificates());

      // Success! Save the handshake and the ALPN protocol.
      String maybeProtocol = connectionSpec.supportsTlsExtensions()
          ? Platform.get().getSelectedProtocol(sslSocket)
          : null;
      socket = sslSocket;
      source = Okio.buffer(Okio.source(socket));
      sink = Okio.buffer(Okio.sink(socket));
      handshake = unverifiedHandshake;
      protocol = maybeProtocol != null
          ? Protocol.get(maybeProtocol)
          : Protocol.HTTP_1_1;
      success = true;
    } catch (AssertionError e) {
      if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
      throw e;
    } finally {
      if (sslSocket != null) {
        Platform.get().afterHandshake(sslSocket);
      }
      if (!success) {
        closeQuietly(sslSocket);
      }
    }
  }

复制代码

经过代码能够看到,首先经过SSLSocketFactory建立SSLSocket,这个SSLSocketFactory就是咱们在构件OkHttpClient的时候传进去的,而后会调用handshake方法进行握手,而后会回HostnameVerifier进行主机验证,这个时候若是返回false就会抛错,最后是对证书的校验。

3总结

回到最开始的那个问题,集团的WebView库使用了集团的网路库,咱们的项目也使用了集团的网络库,在进行网络库设置时,传入了HostnameVerifier进行主机校验,可是这个校验中没有包含WebView中访问的域名,致使出现问题。

关注个人微信公众号

相关文章
相关标签/搜索