Android原生网络库HttpURLConnection分析——HTTPS部分

一、前言

在《Android原生网络库分析——HTTP部分》一文中分析完了HTTP部分,当然其中也包含了网络库中绝大部分共有的基础部分。这一篇中只侧重于HTTPS协议部分,相关基础只会一笔带过,不了解HTTP基础的同学可以先看看前文中的HTTP部分。

应该有不少小伙伴对HTTPS中所涉及到的证书,签名,RSA加密,SSL/TLS等名词是熟悉的陌生人。没关系,通过对Android原生网络库的分析,我们不仅要了解这些名词的意义,还要知道它们是如何应用的。原则还是不变,还是以网络连接、通信与断开为线索进行分析。

二、HTTPS连接

1.openConnection()打开连接

Android原生网络库HttpURLConnection分析——HTTPS部分

OpenHttpsURLConnection.jpg


在详细分析之前,需要再看一个类图结构,以了解 Http们 与 Https们的关系。


Android原生网络库HttpURLConnection分析——HTTPS部分

HttpsUrlConnectionImpl.jpg


从类图可以看到,HttpsUrlConnectionImpl的继承树里包含了HttpUrlConnection,也就是说HttpUrlConnection中的基本功能都是共用的,可能你会觉得这是废话。而有一个关键点在于其还有一个子类HttpUrlConnectionDelegate,这个子类又继承了HttpUrlConnectionImpl,而它的继承树里也有HttpUrlConnection,看起来似乎有点乱了。其实不然,HttpsUrlConnection以及HttpUrlConnection都是抽象类,它们封装了各自协议里最基础接口以及功能。而HttpsUrlConnectionImpl及HttpUrlConnectionImpl是各自协议的具体实现者,并且在HttpsUrlConnectionImpl中的代理类HttpUrlConnectionDelegate其实就指向了HttpUrlConnectionImpl的实例对象,这也体现了Https只需要关心 TLS/SSL 层的事情就可以了,其他与Http相关的事情还是由Http们来完成即可。

好像绕了点,那简单点说就是HttpsUrlConnectionImpl继承HttpUrlConnection是为了代码的复用,而HttpsUrlConnectionImpl包含HttpUrlConnectionImpl的实例对象则是功能复用。

理清了Https们与Http们的关系后就可以后面的流程分析了。从时序图来看,openConnection()最终就是创建了一个HttpsUrlConnectionImpl的实现来返回给调用者。而当HttpsUrlConnectionImpl被创建时,它同时创建好HttpUrlConnectionImpl的代理类。有一点需要关注的是,系统如何判断的协议是否为Https的,看如下代码,其实很简单,在setupStreamHandler()中确定Handler的时候确定的。

<code>void setupStreamHandler() {            ...... // 省略部分代码423424        // Fall back to a built-in stream handler if the user didn't supply one425        if (protocol.equals("file")) {426            streamHandler = new FileHandler();427        } else if (protocol.equals("ftp")) {428            streamHandler = new FtpHandler();429        } else if (protocol.equals("http")) {430            streamHandler = new HttpHandler();431        } else if (protocol.equals("https")) {432            streamHandler = new HttpsHandler();433        } else if (protocol.equals("jar")) {434            streamHandler = new JarHandler();435        }436        if (streamHandler != null) {437            streamHandlers.put(protocol, streamHandler);438        }439    }/<code>

在创建HttpsUrlConnectionImpl实例的过程中,在其父类HttpsUrlConnection中创建了两个重要的类,一个是HostnameVerifier,用于握手时验证主机名的,另一个是SSLSockoetFactory的实例,用于创建SSLSocket的。

<code>protected HttpsURLConnection(URL url) {178        super(url);179        hostnameVerifier = defaultHostnameVerifier;180        sslSocketFactory = defaultSSLSocketFactory;181    }/<code>

先来看SSLSockoetFactory吧。sslSocketFactory被设置成了默认defaultSSLSocketFactory,当然,其也允许用户自己指定SSLSockoetFactory,比如自签名的证书就可以自己设置。再来看看defaultSSLSocketFactory的初始化。

<code>private static SSLSocketFactory defaultSSLSocketFactory = (SSLSocketFactory) SSLSocketFactory111            .getDefault();/<code>

继续看SSLSocketFactory#getDefault()

<code>/**38     * Returns the default {@code SSLSocketFactory} instance. The default is39     * defined by the security property {@code 'ssl.SocketFactory.provider'}.40     *41     * @return the default ssl socket factory instance.42     */43    public static synchronized SocketFactory getDefault() {44        if (defaultSocketFactory != null) { // 单例类的实现45            return defaultSocketFactory;46        }            // Security类所提供的以“ssl.SocketFactory.provider”为属性key的类47        if (defaultName == null) {48            defaultName = Security.getProperty("ssl.SocketFactory.provider");49            if (defaultName != null) {50                ClassLoader cl = Thread.currentThread().getContextClassLoader();51                if (cl == null) {52                    cl = ClassLoader.getSystemClassLoader();53                }54                try {55                    final Class> sfc = Class.forName(defaultName, true, cl);56                    defaultSocketFactory = (SocketFactory) sfc.newInstance();57                } catch (Exception e) {58                    System.logE("Problem creating " + defaultName, e);59                }60            }61        }62        // 默认SSLConext返回的对象实例63        if (defaultSocketFactory == null) {64            SSLContext context;65            try {66                context = SSLContext.getDefault();67            } catch (NoSuchAlgorithmException e) {68                context = null;69            }70            if (context != null) {71                defaultSocketFactory = context.getSocketFactory();72            }73        }            // 直接new一个DefaultSSLSocketFactory,但表明其未安装。74        if (defaultSocketFactory == null) {75            // Use internal implementation76            defaultSocketFactory = new DefaultSSLSocketFactory("No SSLSocketFactory installed");77        }78        return defaultSocketFactory;79    }/<code>

从实现上来看会从三个方面的其中之一。(1)通过Security获取。与之配套的有一个属性文件/libcore/luni/src/main/java/java/security/security.properties,其记录了key=ssl.SocketFactory.provider所对应的类名。可以说是OpenSSLSocketFactoryImpl,其所在的包为org.apache.harmony.xnet.provider.jsse。哦哟,看来是移植的OpenSSL实现咯,要越陷越深了吗。先认识到这里,后面会再见面的。

<code># For regular SSLSockets, we have two implementations:ssl.SocketFactory.provider=org.apache.harmony.xnet.provider.jsse.OpenSSLSocketFactoryImpl#ssl.SocketFactory.provider=org.apache.harmony.xnet.provider.jsse.SSLSocketFactoryImpl/<code>

(2)通过SSLConext来获取

<code>    public final SSLSocketFactory getSocketFactory() {228        return spiImpl.engineGetSocketFactory();229    }/<code>

其中spiImpl是SSLContextSpi所定义的,SSLContextSpi是一个抽象类,其具体实现类是SSLContextImpl,其在engineGetSocketFactory方法返回的是SSLServerSocketFactoryImpl的实例。其包也是org.apache.harmony.xnet.provider.jsse。恩,其实就是上面属性文件中被注释掉的那个。(3)在(1)和(2)都没有结果时,就直接返回默认的类DefaultSSLSocketFactory的实例了。

2.getOutputStream()

相较于HTTP的getOutputStream(),这一部分多了个TLS/SSL的握手部分,然后再加上这里涉及到了另外三方开源库OpenSSL的实现,这里至少需要分成三部分来讲解。为了讲解不过于复杂,先来看前两部分:


Android原生网络库HttpURLConnection分析——HTTPS部分

HttpsGetOutputStream.jpg


(1)Http的连接

前面的1-14步所完成的工作就是HTTP的TCP三步握手,从而使客户端与服务器之间建立起通信连接。这一点和HTTP的相同,因此,这里只做简单的回顾。首先创建的是HttpsUrlConnectionImpl,这里设定了默认的端口号为443。然后就是设定http的相关协议请求行,请求头参数等,最后通过HttpEngine#sendRequest()将请求发出去,看过《Android原生网络库分析——HTTP部分》的同学应该还记得,经过HttpConnection#connect()方法并创建好HttpConnection实例后其实就建立起TCP的连接了。

(2)发起Https的握手请求

Https用于连接的帮助类为HttpsEngines,在其connect()方法里还进一步调用了自身的makeSslConnection()方法,这个方法是私有的也是https特有的,由这里开始发起Https的握手协议。

<code>/**451         * Attempt to make an https connection. Returns true if a452         * connection was reused, false otherwise.453         *454         * @param tlsTolerant If true, assume server can handle common455         * TLS extensions and SSL deflate compression. If false, use456         * an SSL3 only fallback mode without compression.457         */458        private boolean makeSslConnection(boolean tlsTolerant) throws IOException {459            // make an SSL Tunnel on the first message pair of each SSL + proxy connection460            if (connection == null) {461                connection = openSocketConnection();462                if (connection.getAddress().getProxy() != null) {463                    makeTunnel(policy, connection, getRequestHeaders());464                }465            }466467            // if super.makeConnection returned a connection from the468            // pool, sslSocket needs to be initialized here. If it is469            // a new connection, it will be initialized by470            // getSecureSocket below.               // 这里第一次获取时会返回 null471            sslSocket = connection.getSecureSocketIfConnected();472473            // we already have an SSL connection,474            if (sslSocket != null) {475                return true;476            }477            // 带着前面定义好的 SSLSocketFactory 进一步设置 sslSocket,这里应该是 OpenSSLSocketFactoryImpl 478            connection.setupSecureSocket(enclosing.getSSLSocketFactory(), tlsTolerant);479            return false;480        }/<code>

这里进一步调用 HttpConnection#setupSecureSocket()。

<code>/**186     * Create an {@code SSLSocket} and perform the SSL handshake187     * (performing certificate validation.188     *189     * @param sslSocketFactory Source of new {@code SSLSocket} instances.190     * @param tlsTolerant If true, assume server can handle common191     * TLS extensions and SSL deflate compression. If false, use192     * an SSL3 only fallback mode without compression.193     */194    public void setupSecureSocket(SSLSocketFactory sslSocketFactory, boolean tlsTolerant)195            throws IOException {196        // create the wrapper over connected socket           // 创建 SslSocket,其应该为 OpenSSLSocketImpl 的实例197        unverifiedSocket = (SSLSocket) sslSocketFactory.createSocket(socket,198                address.uriHost, address.uriPort, true /* autoClose */);199        // tlsTolerant mimics Chrome's behavior200        if (tlsTolerant && unverifiedSocket instanceof OpenSSLSocketImpl) {201            OpenSSLSocketImpl openSslSocket = (OpenSSLSocketImpl) unverifiedSocket;202            openSslSocket.setUseSessionTickets(true);203            openSslSocket.setHostname(address.uriHost);204            // use SSLSocketFactory default enabled protocols205        } else {206            unverifiedSocket.setEnabledProtocols(new String [] { "SSLv3" });207        }208        // force handshake, which can throw           // 这里会发起https的握手209        unverifiedSocket.startHandshake();210    }/<code>

在这个方法创建了 SslSocket 的创建以及通过其发起了Https的握手。这里已经开始涉及到Native层的操作了,也就是OpenSSL相关的操作。后面的东西会涉及到Native的东西,所以需要点c/c++的基础语法,一点点就好。其实前面已经多次提到OpenSSL了,它是一个TLS/SSL的实现,其代码托管在Giithub上,有兴趣的可以做更深入的了解。在后面的三步握手实现里也会讲到其里面的部分实现。这里还是要继续分析关于创建SslScoket以及发起Https握手前的细节。

a.创建SslSocket以及ssl的上下文指针

SslSocket的是OpenSSLSocketImpl的实例,而sslSocketFactory是OpenSSLSocketFactoryImpl的实现。createSocket有多个版本,根据上面的代码,调用的版本如下。

<code>public Socket createSocket(Socket s, String host, int port, boolean autoClose)93            throws IOException {94        return new OpenSSLSocketImplWrapper(s,95                                            host,96                                            port,97                                            autoClose,98                                            (SSLParametersImpl) sslParameters.clone());99    }/<code> 

OpenSSLSocketImplWrapper是个包装类,其继承自OpenSSLSocketImpl,它的构造函数也是有多个版本的。

<code>protected OpenSSLSocketImpl(Socket socket, String host, int port,160            boolean autoClose, SSLParametersImpl sslParameters) throws IOException {161        this.socket = socket;162        this.wrappedHost = host;163        this.wrappedPort = port;164        this.autoClose = autoClose;165        init(sslParameters);166167        // this.timeout is not set intentionally.168        // OpenSSLSocketImplWrapper.getSoTimeout will delegate timeout169        // to wrapped socket170    }171/<code>

在创建OpenSSLSocketImpl时,除了socket,host,port这些基本参数,还有一个用于SSL的参数sslParameters,其所属类为SSLParametersImpl。在OpenSSLSocketFactoryImpl构造时通过调用SSLParametersImpl#getDefault()对其进行了初始化。

<code>protected static SSLParametersImpl getDefault() throws KeyManagementException {144        SSLParametersImpl result = defaultParameters;145        if (result == null) {146            // single-check idiom147            defaultParameters = result = new SSLParametersImpl(null,148                                                               null,149                                                               null,150                                                               new ClientSessionContext(),151                                                               new ServerSessionContext());152        }153        return (SSLParametersImpl) result.clone();154    }/<code>

其创建了用于会话的上下文,且包含了client与server的。这里只以Android客户端来分析,那就只关注ClientSessionContext即可。

<code>public class ClientSessionContext extends AbstractSessionContext {...}abstract class AbstractSessionContext implements SSLSessionContext {4041    volatile int maximumSize;42    volatile int timeout;4344    final long sslCtxNativePointer = NativeCrypto.SSL_CTX_new();/<code>

ClientSessionContext继承自AbstractSessionContext,而在AbstractSessionContext初始化时就会初始化Native层的上下文指针,且最终把值赋给上层Java的整型变量sslCtxNativePointer。我们把这一过程可以叫做SSL的上下文创建。

那就来看看SSL_CTX_new()的实现。这里必须要补充一下的是NativeCrypto.java的 jni 层对应的文件是 /libcore/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp。SSL_CTX_new()函数在其里面的实现如下:

<code>/*5998 * public static native int SSL_CTX_new();5999 */6000static jlong NativeCrypto_SSL_CTX_new(JNIEnv* env, jclass) {            // 创建上下文6001    Unique_SSL_CTX sslCtx(SSL_CTX_new(SSLv23_method()));6002    if (sslCtx.get() == NULL) {6003        throwExceptionIfNecessary(env, "SSL_CTX_new");6004        return 0;6005    }            // 设置上下文选项6006    SSL_CTX_set_options(sslCtx.get(),6007                        SSL_OP_ALL6008                        // Note: We explicitly do not allow SSLv2 to be used.6009                        | SSL_OP_NO_SSLv26010                        // We also disable session tickets for better compatibility b/26828766011                        | SSL_OP_NO_TICKET6012                        // We also disable compression for better compatibility b/2710492 b/27104976013                        | SSL_OP_NO_COMPRESSION6014                        // Because dhGenerateParameters uses DSA_generate_parameters_ex6015                        | SSL_OP_SINGLE_DH_USE6016                        // Because ecGenerateParameters uses a fixed named curve6017                        | SSL_OP_SINGLE_ECDH_USE);60186019    int mode = SSL_CTX_get_mode(sslCtx.get());6020    /*6021     * Turn on "partial write" mode. This means that SSL_write() will6022     * behave like Posix write() and possibly return after only6023     * writing a partial buffer. Note: The alternative, perhaps6024     * surprisingly, is not that SSL_write() always does full writes6025     * but that it will force you to retry write calls having6026     * preserved the full state of the original call. (This is icky6027     * and undesirable.)6028     */6029    mode |= SSL_MODE_ENABLE_PARTIAL_WRITE;60306031    // Reuse empty buffers within the SSL_CTX to save memory6032    mode |= SSL_MODE_RELEASE_BUFFERS;6033    // 设置模式6034    SSL_CTX_set_mode(sslCtx.get(), mode);6035    // 设置一系列的 callback6036    SSL_CTX_set_cert_verify_callback(sslCtx.get(), cert_verify_callback, NULL);6037    SSL_CTX_set_info_callback(sslCtx.get(), info_callback);6038    SSL_CTX_set_client_cert_cb(sslCtx.get(), client_cert_cb);6039    SSL_CTX_set_tmp_rsa_callback(sslCtx.get(), tmp_rsa_callback);6040    SSL_CTX_set_tmp_dh_callback(sslCtx.get(), tmp_dh_callback);6041    SSL_CTX_set_tmp_ecdh_callback(sslCtx.get(), tmp_ecdh_callback);60426043    JNI_TRACE("NativeCrypto_SSL_CTX_new => %p", sslCtx.get());6044    return (jlong) sslCtx.release();6045}/<code>

这里又调用了另一个SSL_CTX_new()函数,它的实现在 ssl_lib.c 文件中,其将创建用于会话的上下文指针 SSL_CTX*,其是typedef定义,即ssl_ctx_st。而这里还要注意其传入参数为SSLv23_method(),这是一个函数指针,其返回值是SSL_METHOD,而SSL_METHOD其是结构体 ssl_method_st,其定义如下。

<code>/* Used to hold functions for SSLv2 or SSLv3/TLSv1 functions */409struct ssl_method_st410 {411 int version;412 int (*ssl_new)(SSL *s);413 void (*ssl_clear)(SSL *s);414 void (*ssl_free)(SSL *s);415 int (*ssl_accept)(SSL *s);416 int (*ssl_connect)(SSL *s);417 int (*ssl_read)(SSL *s,void *buf,int len);418 int (*ssl_peek)(SSL *s,void *buf,int len);419 int (*ssl_write)(SSL *s,const void *buf,int len);420 int (*ssl_shutdown)(SSL *s);421 int (*ssl_renegotiate)(SSL *s);422 int (*ssl_renegotiate_check)(SSL *s);423 long (*ssl_get_message)(SSL *s, int st1, int stn, int mt, long424     max, int *ok);425 int (*ssl_read_bytes)(SSL *s, int type, unsigned char *buf, int len,426     int peek);427 int (*ssl_write_bytes)(SSL *s, int type, const void *buf_, int len);428 int (*ssl_dispatch_alert)(SSL *s);429 long (*ssl_ctrl)(SSL *s,int cmd,long larg,void *parg);430 long (*ssl_ctx_ctrl)(SSL_CTX *ctx,int cmd,long larg,void *parg);431 const SSL_CIPHER *(*get_cipher_by_char)(const unsigned char *ptr);432 int (*put_cipher_by_char)(const SSL_CIPHER *cipher,unsigned char *ptr);433 int (*ssl_pending)(const SSL *s);434 int (*num_ciphers)(void);435 const SSL_CIPHER *(*get_cipher)(unsigned ncipher);436 const struct ssl_method_st *(*get_ssl_method)(int version);437 long (*get_timeout)(void);438 struct ssl3_enc_method *ssl3_enc; /* Extra SSLv3/TLS stuff */439 int (*ssl_version)(void);440 long (*ssl_callback_ctrl)(SSL *s, int cb_id, void (*fp)(void));441 long (*ssl_ctx_callback_ctrl)(SSL_CTX *s, int cb_id, void (*fp)(void));442 };/<code>

上面的定义比较长,只作了解即可,在后面的过程中会慢慢熟悉的。当然,其实从这个定义其上可以看到 TLS/SSL 连接或者认证的一些影子了。而对于SSLv23_method()本身来说,既然是一个函数指针,那么一定有其实现的地方。

<code>IMPLEMENT_ssl23_meth_func(SSLv23_method,89          ssl23_accept,90          ssl23_connect,91          ssl23_get_method)/<code>

由一个宏来实现的,这是 C 语言的惯用技巧。还是来看看吧。

<code>#define IMPLEMENT_ssl23_meth_func(func_name, s_accept, s_connect, s_get_meth) \\702const SSL_METHOD *func_name(void)  \\703 { \\704 static const SSL_METHOD func_name##_data= { \\705 TLS1_2_VERSION, \\706 tls1_new, \\707 tls1_clear, \\708 tls1_free, \\709 s_accept, \\710 s_connect, \\711 ssl23_read, \\712 ssl23_peek, \\713 ssl23_write, \\714 ssl_undefined_function, \\715 ssl_undefined_function, \\716 ssl_ok, \\717 ssl3_get_message, \\718 ssl3_read_bytes, \\719 ssl3_write_bytes, \\720 ssl3_dispatch_alert, \\721 ssl3_ctrl, \\722 ssl3_ctx_ctrl, \\723 ssl23_get_cipher_by_char, \\724 ssl23_put_cipher_by_char, \\725 ssl_undefined_const_function, \\726 ssl23_num_ciphers, \\727 ssl23_get_cipher, \\728 s_get_meth, \\729 ssl23_default_timeout, \\730 &ssl3_undef_enc_method, \\731 ssl_undefined_void_function, \\732 ssl3_callback_ctrl, \\733 ssl3_ctx_callback_ctrl, \\734 }; \\735 return &func_name##_data; \\736 }/<code>

也就是说最终定义了一个类型为ssl_method_st的 SSLv23_method_data 的变量,它的每一项参数也将赋值结构体中ssl_method_st的每一个成员。另外要注意上面的参数ssl23_accept和ssl23_connect。对于服务端需要 s_accept参数,而客户端则只需要ssl23_connect。同时,也可以看出,这两个也是函数指针,其中ssl23_connect的实现在 s23_clnt.c 文件中,其实现后面再说。至于上下文ssl_ctx_st,其也是一个结构体,其定义也是相当的长,这里就先不貼出来了,后面如果有需要再看情况。这里花费很大的力气就是为了弄清楚这个参数 SSLv23_method的定义,因为这是一个很至关重要的参数,其决定了用于连接的协议以及版本为TLS1_2_VERSION,以及将来用于连接的 ssl23_connect。而关于 ssl_lib.c 中SSL_CTX_new()的实现,也比较长,也不贴了。只要知道函数指针SSLv23_method会赋值为 SSL_CTX指针的 method 的成员变量即可。

b.发起Https握手

终于要握手了,即调用OpenSSLSocketImpl#startHandshake()。

(3)握手

Android原生网络库HttpURLConnection分析——HTTPS部分

HttpsGetOutputStream2.jpg


握手是https里面最核心的部分,握手过程中最核心的是传递加密方法、密钥以及证书验证等。先来看一看握手的上层Java代码。前方高能,又是非常长的一段代码。为了简单,可以不用理会整个完整的代码,对着时序图看看关键注释即可。

<code>/**242     * Starts a TLS/SSL handshake on this connection using some native methods243     * from the OpenSSL library. It can negotiate new encryption keys, change244     * cipher suites, or initiate a new session. The certificate chain is245     * verified if the correspondent property in java.Security is set. All246     * listeners are notified at the end of the TLS/SSL handshake.247     */248    @Override public synchronized void startHandshake() throws IOException {249        synchronized (handshakeLock) {250            checkOpen();// 状态检查251            if (!handshakeStarted) {252                handshakeStarted = true;253            } else {254                return;255            }256        }257258        // note that this modifies the global seed, not something specific to the connection259        final int seedLengthInBytes = NativeCrypto.RAND_SEED_LENGTH_IN_BYTES; // RAND_SEED_LENGTH_IN_BYTES = 1024           // 获取或者生成用于加密的随机数260        final SecureRandom secureRandom = sslParameters.getSecureRandomMember();261        if (secureRandom == null) {262            NativeCrypto.RAND_load_file("/dev/urandom", seedLengthInBytes);263        } else {264            NativeCrypto.RAND_seed(secureRandom.generateSeed(seedLengthInBytes));265        }266                   // 这里是客户端,所以为 true267        final boolean client = sslParameters.getUseClientMode();268           // 从客户端获取上下文指针   269        final long sslCtxNativePointer = (client) ?270            sslParameters.getClientSessionContext().sslCtxNativePointer :271            sslParameters.getServerSessionContext().sslCtxNativePointer;272273        this.sslNativePointer = 0;274        boolean exception = true;275        try {               // 创建 SSL 指针276            sslNativePointer = NativeCrypto.SSL_new(sslCtxNativePointer);277            guard.open("close");278               // npn 协议是什么协议?ALPN(Application-Layer Protocol Negotiation)也是 TLS 层的扩展,用于协商应用层使用的协议。它的前身是 NPN,最初用于支持 Google SPDY 协议(现已标准化为 HTTP/2)。279            if (npnProtocols != null) {280                NativeCrypto.SSL_CTX_enable_npn(sslCtxNativePointer);281            }282283            // setup server certificates and private keys.284            // clients will receive a call back to request certificates.               // 服务器端相关内容,主要就是设置证书以及私钥,只做了解即可。285            if (!client) {286                Set<string> keyTypes = new HashSet<string>();287                for (String enabledCipherSuite : enabledCipherSuites) {288                    if (enabledCipherSuite.equals(NativeCrypto.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) {289                        continue;290                    }291                    String keyType = CipherSuite.getByName(enabledCipherSuite).getServerKeyType();292                    if (keyType != null) {293                        keyTypes.add(keyType);294                    }295                }296                for (String keyType : keyTypes) {297                    try {298                        setCertificate(sslParameters.getKeyManager().chooseServerAlias(keyType,299                                                                                       null,300                                                                                       this));301                    } catch (CertificateEncodingException e) {302                        throw new IOException(e);303                    }304                }305            }306               // 使能协议,这里默认是SSLv3、TLS307            NativeCrypto.setEnabledProtocols(sslNativePointer, enabledProtocols);               // 设置密码组,如 md5,sha等308            NativeCrypto.setEnabledCipherSuites(sslNativePointer, enabledCipherSuites);309            if (useSessionTickets) {310                NativeCrypto.SSL_clear_options(sslNativePointer, NativeCrypto.SSL_OP_NO_TICKET);311            }312            if (hostname != null) {313                NativeCrypto.SSL_set_tlsext_host_name(sslNativePointer, hostname);314            }315316            boolean enableSessionCreation = sslParameters.getEnableSessionCreation();317            if (!enableSessionCreation) {318                NativeCrypto.SSL_set_session_creation_enabled(sslNativePointer,319                                                              enableSessionCreation);320            }321322            AbstractSessionContext sessionContext;323            OpenSSLSessionImpl sessionToReuse;324            if (client) {325                // look for client session to reuse326                ClientSessionContext clientSessionContext = sslParameters.getClientSessionContext();327                sessionContext = clientSessionContext;328                sessionToReuse = getCachedClientSession(clientSessionContext);329                if (sessionToReuse != null) {330                    NativeCrypto.SSL_set_session(sslNativePointer,331                                                 sessionToReuse.sslSessionNativePointer);332                }333            } else {334                sessionContext = sslParameters.getServerSessionContext();335                sessionToReuse = null;336            }337338            // setup peer certificate verification               // 设置证书,目前只是服务端339            if (client) {340                // TODO support for anonymous cipher would require us to341                // conditionally use SSL_VERIFY_NONE342            } else {343                // needing client auth takes priority...344                boolean certRequested;345                if (sslParameters.getNeedClientAuth()) {346                    NativeCrypto.SSL_set_verify(sslNativePointer,347                                                NativeCrypto.SSL_VERIFY_PEER348                                                | NativeCrypto.SSL_VERIFY_FAIL_IF_NO_PEER_CERT);349                    certRequested = true;350                // ... over just wanting it...351                } else if (sslParameters.getWantClientAuth()) {352                    NativeCrypto.SSL_set_verify(sslNativePointer,353                                                NativeCrypto.SSL_VERIFY_PEER);354                    certRequested = true;355                // ... and it defaults properly so don't call SSL_set_verify in the common case.356                } else {357                    certRequested = false;358                }359360                if (certRequested) {361                    X509TrustManager trustManager = sslParameters.getTrustManager();362                    X509Certificate[] issuers = trustManager.getAcceptedIssuers();363                    if (issuers != null && issuers.length != 0) {364                        byte[][] issuersBytes;365                        try {366                            issuersBytes = NativeCrypto.encodeIssuerX509Principals(issuers);367                        } catch (CertificateEncodingException e) {368                            throw new IOException("Problem encoding principals", e);369                        }                           // 下发证书链给客户端370                        NativeCrypto.SSL_set_client_CA_list(sslNativePointer, issuersBytes);371                    }372                }373            }374375            // Temporarily use a different timeout for the handshake process               // 握手时间376            int savedReadTimeoutMilliseconds = getSoTimeout();377            int savedWriteTimeoutMilliseconds = getSoWriteTimeout();378            if (handshakeTimeoutMilliseconds >= 0) {379                setSoTimeout(handshakeTimeoutMilliseconds);380                setSoWriteTimeout(handshakeTimeoutMilliseconds);381            }382383            // TLS Channel ID               // 设置 channel id384            if (client) {385                // Client-side TLS Channel ID386                if (channelIdPrivateKey != null) {387                    NativeCrypto.SSL_set1_tls_channel_id(sslNativePointer, channelIdPrivateKey);388                }389            } else {390                // Server-side TLS Channel ID391                if (channelIdEnabled) {392                    NativeCrypto.SSL_enable_tls_channel_id(sslNativePointer);393                }394            }395396            int sslSessionNativePointer;397            try {                   // 握手398                sslSessionNativePointer = NativeCrypto.SSL_do_handshake(sslNativePointer,399                        socket.getFileDescriptor$(), this, getSoTimeout(), client, npnProtocols);400            } catch (CertificateException e) {401                SSLHandshakeException wrapper = new SSLHandshakeException(e.getMessage());402                wrapper.initCause(e);403                throw wrapper;404            }405            byte[] sessionId = NativeCrypto.SSL_SESSION_session_id(sslSessionNativePointer);406            if (sessionToReuse != null && Arrays.equals(sessionToReuse.getId(), sessionId)) {407                this.sslSession = sessionToReuse;408                sslSession.lastAccessedTime = System.currentTimeMillis();409                NativeCrypto.SSL_SESSION_free(sslSessionNativePointer);410            } else {411                if (!enableSessionCreation) {412                    // Should have been prevented by NativeCrypto.SSL_set_session_creation_enabled413                    throw new IllegalStateException("SSL Session may not be created");414                }                   // 证书链处理415                X509Certificate[] localCertificates416                        = createCertChain(NativeCrypto.SSL_get_certificate(sslNativePointer));417                X509Certificate[] peerCertificates418                        = createCertChain(NativeCrypto.SSL_get_peer_cert_chain(sslNativePointer));419                this.sslSession = new OpenSSLSessionImpl(sslSessionNativePointer, localCertificates,420                        peerCertificates, getPeerHostName(), getPeerPort(), sessionContext);421                // if not, putSession later in handshakeCompleted() callback422                if (handshakeCompleted) {423                    sessionContext.putSession(sslSession);424                }425            }426427            // Restore the original timeout now that the handshake is complete428            if (handshakeTimeoutMilliseconds >= 0) {429                setSoTimeout(savedReadTimeoutMilliseconds);430                setSoWriteTimeout(savedWriteTimeoutMilliseconds);431            }432433            // if not, notifyHandshakeCompletedListeners later in handshakeCompleted() callback                  // 完成握手并发出通知434            if (handshakeCompleted) {435                notifyHandshakeCompletedListeners();436            }437438            exception = false;439        } catch (SSLProtocolException e) {440            throw new SSLHandshakeException(e);441        } finally {442            // on exceptional exit, treat the socket as closed443            if (exception) {444                close();445            }446        }447    }/<string>/<string>/<code>

这是一段超长的代码,还是那句话,不要畏难,长归长,一句一句分析下来就短了。当然其步骤也是比较多的,如下,大概有 12 步。简单的就不做展开了,复杂的就做更深的了解

a.获取或者生成用于加密的随机数

b.从客户端获取上下文指针

c.创建 SSL 指针

调用SSL_new()函数将创建指针SSL*,即ssl_st。虽然很重要,但没有什么特别关键的就不展开了。

d.使能协议

e.设置密码套件

<code>public static String[] getDefaultCipherSuites() {712        return new String[] {713            "SSL_RSA_WITH_RC4_128_MD5",714            "SSL_RSA_WITH_RC4_128_SHA",715            "TLS_RSA_WITH_AES_128_CBC_SHA",716            "TLS_RSA_WITH_AES_256_CBC_SHA",717            "TLS_ECDH_ECDSA_WITH_RC4_128_SHA",718            "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA",719            "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA",720            "TLS_ECDH_RSA_WITH_RC4_128_SHA",721            "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA",722            "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA",723            "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",724            "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",725            "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",726            "TLS_ECDHE_RSA_WITH_RC4_128_SHA",727            "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",728            "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",729            "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",730            "TLS_DHE_RSA_WITH_AES_256_CBC_SHA",731            "TLS_DHE_DSS_WITH_AES_128_CBC_SHA",732            "TLS_DHE_DSS_WITH_AES_256_CBC_SHA",733            "SSL_RSA_WITH_3DES_EDE_CBC_SHA",734            "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA",735            "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA",736            "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",737            "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",738            "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA",739            "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA",740            "SSL_RSA_WITH_DES_CBC_SHA",741            "SSL_DHE_RSA_WITH_DES_CBC_SHA",742            "SSL_DHE_DSS_WITH_DES_CBC_SHA",743            "SSL_RSA_EXPORT_WITH_RC4_40_MD5",744            "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",745            "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",746            "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA",747            TLS_EMPTY_RENEGOTIATION_INFO_SCSV748        };749    }/<code>

f.设置hostname

g.创建会话

会话创建时,做了一个很重要的工作就是给 SSL 指针中handshake_func赋值。

<code>s->handshake_func=meth->ssl_connect/<code>

这个 meth 就是上下文的 meth,而 ssl_connect 根据前面的分析,其指向的 s23_clnt.c 中的 ssl23_connect() 函数。

h.设置握手时间

i.设置频道id

j.握手

以下是一次握手过程所需要经历的状态,每个状态基本对应一个函数来处理。

Android原生网络库HttpURLConnection分析——HTTPS部分

StatechartHandshake.jpg


<code>int ssl23_connect(SSL *s)146 {147 BUF_MEM *buf=NULL;148 unsigned long Time=(unsigned long)time(NULL);149 void (*cb)(const SSL *ssl,int type,int val)=NULL;150 int ret= -1;151 int new_state,state;152153 RAND_add(&Time,sizeof(Time),0);154 ERR_clear_error();155 clear_sys_error();156157 if (s->info_callback != NULL)158     cb=s->info_callback;159 else if (s->ctx->info_callback != NULL)160     cb=s->ctx->info_callback;161162 s->in_handshake++;// 计数加 1        // SSL 初始化,其 state 将为 SSL_ST_BEFORE|SSL_ST_CONNECT163 if (!SSL_in_init(s) || SSL_in_before(s)) SSL_clear(s);164165 for (;;)166     {167     state=s->state;168169     switch(s->state)170         {171     case SSL_ST_BEFORE:172     case SSL_ST_CONNECT:173     case SSL_ST_BEFORE|SSL_ST_CONNECT: // 第一次循环时进入174     case SSL_ST_OK|SSL_ST_CONNECT:175176         if (s->session != NULL)177             {178             SSLerr(SSL_F_SSL23_CONNECT,SSL_R_SSL23_DOING_SESSION_ID_REUSE);179             ret= -1;180             goto end;181             }182         s->server=0;            // 通知回调函数,开始握手了183         if (cb != NULL) cb(s,SSL_CB_HANDSHAKE_START,1);184185         /* s->version=TLS1_VERSION; */186         s->type=SSL_ST_CONNECT;187188         if (s->init_buf == NULL)189             {190             if ((buf=BUF_MEM_new()) == NULL)191                 {192                 ret= -1;193                 goto end;194                 }195             if (!BUF_MEM_grow(buf,SSL3_RT_MAX_PLAIN_LENGTH))196                 {197                 ret= -1;198                 goto end;199                 }200             s->init_buf=buf;201             buf=NULL;202             }203204         if (!ssl3_setup_buffers(s)) { ret= -1; goto end; }205206         ssl3_init_finished_mac(s);207        // 设置状态为 SSL23_ST_CW_CLNT_HELLO_A,下次循环迭代就会进入 case SSL23_ST_CW_CLNT_HELLO_A.208         s->state=SSL23_ST_CW_CLNT_HELLO_A;209         s->ctx->stats.sess_connect++;210         s->init_num=0;211         break;212213     case SSL23_ST_CW_CLNT_HELLO_A:// 第二次循环214     case SSL23_ST_CW_CLNT_HELLO_B:215216         s->shutdown=0;            // 发送出 hello217         ret=ssl23_client_hello(s);218         if (ret <= 0) goto end;           // 置状态为 SSL23_ST_CR_SRVR_HELLO_A219         s->state=SSL23_ST_CR_SRVR_HELLO_A;220         s->init_num=0;221222         break;223224     case SSL23_ST_CR_SRVR_HELLO_A:// 第三次循环225     case SSL23_ST_CR_SRVR_HELLO_B:           // 获取服务器的的 hello226         ret=ssl23_get_server_hello(s);227         if (ret >= 0) cb=NULL;228         goto end;229         /* break; */230231     default:232         SSLerr(SSL_F_SSL23_CONNECT,SSL_R_UNKNOWN_STATE);233         ret= -1;234         goto end;235         /* break; */236         }237238     if (s->debug) { (void)BIO_flush(s->wbio); }239240     if ((cb != NULL) && (s->state != state))241         {242         new_state=s->state;243         s->state=state;244         cb(s,SSL_CB_CONNECT_LOOP,1);245         s->state=new_state;246         }247     }248end:249 s->in_handshake--;250 if (buf != NULL)251     BUF_MEM_free(buf);252 if (cb != NULL)253     cb(s,SSL_CB_CONNECT_EXIT,ret);254 return(ret);255 }/<code>

其主要流程是 ssl 初始化 ——> 客户端发送 Hello ——> 获取服务端的 Hello。

客户端发送hello

<code>static int ssl23_client_hello(SSL *s)273 {        .......        // 随机数357     p=s->s3->client_random;358     Time=(unsigned long)time(NULL);     /* Time */359     l2n(Time,p);        ......401     // 版本号402     s->client_version = version;403404     if (ssl2_compat)405         {406         /* create SSL 2.0 compatible Client Hello */407408         /* two byte record header will be written last */            .......464         }465     else466         {467         /* create Client Hello in SSL 3.0/TLS 1.0 format */468469         /* do the record header (5 bytes) and handshake message header (4 bytes) last */470         d = p = &(buf[9]);471472         *(p++) = version_major;473         *(p++) = version_minor;474475         /* Random stuff */476         memcpy(p, s->s3->client_random, SSL3_RANDOM_SIZE);477         p += SSL3_RANDOM_SIZE;478479         /* Session ID (zero since there is no reuse) */480         *(p++) = 0;481         // 所支持的 ciphers 套件482         /* Ciphers supported (using SSL 3.0/TLS 1.0 format) */483         i=ssl_cipher_list_to_bytes(s,SSL_get_ciphers(s),&(p[2]),ssl3_put_cipher_by_char);498         s2n(i,p);499         p+=i;500            .......534535         /* fill in 4-byte handshake header */536         d=&(buf[5]);            // 消息类型537         *(d++)=SSL3_MT_CLIENT_HELLO;538         l2n3(l,d);539540         l += 4;541565         ssl3_finish_mac(s,&(buf[5]), s->init_num - 5);566         }567     // 置为 SSL23_ST_CW_CLNT_HELLO_B568     s->state=SSL23_ST_CW_CLNT_HELLO_B;569     s->init_off=0;570     }571572 /* SSL3_ST_CW_CLNT_HELLO_B */        // 写入数据573 ret = ssl23_write_bytes(s);574575 if ((ret >= 2) && s->msg_callback)576     {577     /* Client Hello has been sent; tell msg_callback */578         // 回调579     if (ssl2_compat)580         s->msg_callback(1, SSL2_VERSION, 0, s->init_buf->data+2, ret-2, s, s->msg_callback_arg);581     else582         s->msg_callback(1, version, SSL3_RT_HANDSHAKE, s->init_buf->data+5, ret-5, s, s->msg_callback_arg);583     }584585 return ret;586 }/<code>

客户端发送 hello ,主要是发送了随机数,支持的密钥算法,SSL/TLS的版本号,当然还有消息类型SSL3_MT_CLIENT_HELLO。服务端在收到SSL3_MT_CLIENT_HELLO时会作出回应,也就是发送服务端的HELLO。服务端会在 s23_srvr.c 的 ssl23_get_client_hello 中处理。这里只关注 Android 端,因此不展开服务端的。我们只要知道服务端会从 SSL3_MT_CLIENT_HELLO 传过来的 Support Ciphers 里确定一份加密套件,这个套件决定了后续加密和生成摘要时具体使用哪些算法,另外还会生成一份随机数 Random2。

获取服务端的 Hello

<code>static int ssl23_get_server_hello(SSL *s)589 {    ......    // 读取数据595 n=ssl23_read_bytes(s,7);596597 if (n != 7) return(n);598 p=s->packet;599600 memcpy(buf,p,n);601       // SSL2_MT_SERVER_HELLO消息602 if ((p[0] & 0x80) && (p[2] == SSL2_MT_SERVER_HELLO) &&603     (p[5] == 0x00) && (p[6] == 0x02))604     {           ......672     }673 else if (p[1] == SSL3_VERSION_MAJOR &&674          p[2] <= TLS1_2_VERSION_MINOR &&675          ((p[0] == SSL3_RT_HANDSHAKE && p[5] == SSL3_MT_SERVER_HELLO) ||676           (p[0] == SSL3_RT_ALERT && p[3] == 0 && p[4] == 2)))677     {678     /* we have sslv3 or tls1 (server hello or alert) */679     // 服务端选定的协议版本680     if ((p[2] == SSL3_VERSION_MINOR) &&          ......693         }694     else if ((p[2] == TLS1_VERSION_MINOR) &&695         !(s->options & SSL_OP_NO_TLSv1))696         {697         ......699         }700     else if ((p[2] == TLS1_1_VERSION_MINOR) &&701         !(s->options & SSL_OP_NO_TLSv1_1))702         {703         ......705         }706     else if ((p[2] == TLS1_2_VERSION_MINOR) &&707         !(s->options & SSL_OP_NO_TLSv1_2))708         {//假设是这个版本吧,因为现在主要用的就是这个版本了709         s->version=TLS1_2_VERSION;                        // 这里的 method 被重新定义了为 TLSv1_2_client_method,它也是一个宏实现,其最重要和 ssl_connect 指向了 ssl3_connect.710         s->method=TLSv1_2_client_method();711         }712     else713         {714         SSLerr(SSL_F_SSL23_GET_SERVER_HELLO,SSL_R_UNSUPPORTED_PROTOCOL);715         goto err;716         }717718     if (p[0] == SSL3_RT_ALERT && p[5] != SSL3_AL_WARNING)719         {720         /* fatal alert */721722         void (*cb)(const SSL *ssl,int type,int val)=NULL;723         int j;724725         if (s->info_callback != NULL)726             cb=s->info_callback;727         else if (s->ctx->info_callback != NULL)728             cb=s->ctx->info_callback;729730         i=p[5];731         if (cb != NULL)732             {733             j=(i<<8)|p[6];734             cb(s,SSL_CB_READ_ALERT,j);735             }736737         if (s->msg_callback)738             s->msg_callback(0, s->version, SSL3_RT_ALERT, p+5, 2, s, s->msg_callback_arg);739740         s->rwstate=SSL_NOTHING;741         SSLerr(SSL_F_SSL23_GET_SERVER_HELLO,SSL_AD_REASON_OFFSET+p[6]);742         goto err;743         }744745     if (!ssl_init_wbio_buffer(s,1)) goto err;746747     /* we are in this state */        // 置状态为748     s->state=SSL3_ST_CR_SRVR_HELLO_A;749        ......761     改变 handshake_func 的值为 s->method->ssl_connect762     s->handshake_func=s->method->ssl_connect;763     }764 else765     {766     SSLerr(SSL_F_SSL23_GET_SERVER_HELLO,SSL_R_UNKNOWN_PROTOCOL);767     goto err;768     }782      // 再调用 SSL_connect783 return(SSL_connect(s));784err:785 return(-1);786 }/<code>

这一段代码只读了 7 个字节,主要就是确定了协议版本以及用于执行继续握手的handshake_fun的指向。根据代码里的分析此时是指向的是 ssl3_connect。另外,此时SSL的状态为 SSL3_ST_CR_SRVR_HELLO_A。在 ssl3_connect中,对于 SSL3_ST_CR_SRVR_HELLO_A 的处理是调用 ssl3_get_server_hello()

<code>int ssl3_get_server_hello(SSL *s)892 {    ......948 /* load the server hello data */949 /* load the server random */    // 获取服务器的随机数950 memcpy(s->s3->server_random,p,SSL3_RANDOM_SIZE);951 p+=SSL3_RANDOM_SIZE;        ......        // 获取加密算法1016    c=ssl_get_cipher_by_char(s,p);        .......        // 1032    p+=ssl_put_cipher_by_char(s,NULL,NULL);        .....1146    return(1);1147f_err:1148    ssl3_send_alert(s,SSL3_AL_FATAL,al);1149err:1150    return(-1);1151    }/<code>

证书校验

ssl3_get_server_hello()函数主要是读取了从服务器传递过来的随机数以及所选择的加密算法。此时就有两个随机数了。ssl3_get_server_hello() 执行完成后,就会将 s->state 的置为 SSL3_ST_CR_CERT_A。对于SSL3_ST_CR_CERT_A的状态调用的是 ssl3_get_server_certificate()函数

<code>int ssl3_get_server_certificate(SSL *s)1154    {1165    n=s->method->ssl_get_message(s,1166        SSL3_ST_CR_CERT_A,1167        SSL3_ST_CR_CERT_B,1168        -1,1169        s->max_cert_list,1170        &ok);11711203    for (nc=0; nc<llen>/<code>

主要就是检验证书,并获取公钥。握手的后续流程中对于双向验证可能还要发送DH参数,以及客户端的证书给服务器进行校验。接收服务器的 Hello Done消息等,这些就一笔带过了。在 ssl3_send_client_key_exchange() 函数会生成第三个随机数,然后会用服务器下发的证书中RSA的公钥对其进行加密,并传递给服务器。自此客户端与服务器双方都有三个随机数,而利用这三个随机数就可以各自生成对称加密算法的密钥,最后通过这个对称加密算法来进行通信。

k.获取证书构建 OpenSSLSeesionImpl

l.上层完成握手并发出通知

三、总结

1.SSL/TLS 是位于传输层与应用层之间的,其主要的职责就是完成握手协议以及实现数据的加解密传输。

2.Android 中的 SSL/TLS 是移植的 OpenSSL的实现,一个重磅的开源库,想要深了解的同学不妨看看其Github

3.握手是在 TCP 连接建立起来之后才进行的,这也应证了第1点其确实应该处于应用层与传输层之间。4.握手的核心流程是:客户端hello(随机数1,密钥套件,协议版本) -> 服务端Hello(选择的密钥,协议版本,随机数2,并下发证书) -> 服务端完成Hello -> 客户端校验证书 -> 客户端生成随机数3并用公钥加密 -> 完成握手 -> 客户端与服务端各自用三个随机数生成对称密钥进行加密通信

文章前后共持续了半个月左右才完成,然而由于水平有限,文章中难免存在纰漏以及错误之处,还请各位看到文章的同学多多包含并帮忙指出,非常感谢。


分享到:


相關文章: