转载

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

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

1.什么是Https

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

1.1为什么使用https

先说传统http有什么缺陷:

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

(2)不验证通信方的身份,遭遇伪装。

(3)无法证明报文完整性,可能被篡改。(比如中间人攻击,你用的charles就是这样)

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

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

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

所以HTTP+加密+完整性保护+认证=HTTPS

1.2 SSL

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

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

(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就会抛错,最后是对证书的校验。

原文  https://juejin.im/post/5db54a18e51d4529e839489b
正文到此结束
Loading...