一、前言
在《Android原生网络库分析——HTTP部分》一文中分析完了HTTP部分,当然其中也包含了网络库中绝大部分共有的基础部分。这一篇中只侧重于HTTPS协议部分,相关基础只会一笔带过,不了解HTTP基础的同学可以先看看前文中的HTTP部分。
应该有不少小伙伴对HTTPS中所涉及到的证书,签名,RSA加密,SSL/TLS等名词是熟悉的陌生人。没关系,通过对Android原生网络库的分析,我们不仅要了解这些名词的意义,还要知道它们是如何应用的。原则还是不变,还是以网络连接、通信与断开为线索进行分析。
二、HTTPS连接
1.openConnection()打开连接
![Android原生网络库HttpURLConnection分析——HTTPS部分](http://p2.ttnews.xyz/loading.gif)
OpenHttpsURLConnection.jpg
在详细分析之前,需要再看一个类图结构,以了解 Http们 与 Https们的关系。
![Android原生网络库HttpURLConnection分析——HTTPS部分](http://p2.ttnews.xyz/loading.gif)
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的实现,这里至少需要分成三部分来讲解。为了讲解不过于复杂,先来看前两部分:
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)握手
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.握手
以下是一次握手过程所需要经历的状态,每个状态基本对应一个函数来处理。
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并用公钥加密 -> 完成握手 -> 客户端与服务端各自用三个随机数生成对称密钥进行加密通信
文章前后共持续了半个月左右才完成,然而由于水平有限,文章中难免存在纰漏以及错误之处,还请各位看到文章的同学多多包含并帮忙指出,非常感谢。
閱讀更多 仰簡 的文章