本文主要是介绍OpenJDK8源码分析SSL握手流程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
准备
说明
- 示例项目地址:https://github.com/shenjy24/jackal-ssl
- JDK版本:OpenJDK8。
- 系统:MAC
- IDE: IDEA
安装OpenJDK
- 安装JDK
brew tap AdoptOpenJDK/openjdk
brew install caskroom/cask/brew-cask
brew cask install adoptopenjdk8
- IDEA更换JDK,使用openjdk替换
sun jdk
。
Wireshark抓包
使用Wireshark抓包可以先了解到SSL通讯的大体流程,可以参考 Wireshark 抓包理解 HTTPS 请求流程 ,这篇文章详细分析了整个HTTPS通讯流程,包括TCP三次握手、SSL握手环节、TCP四次挥手。
SSL通信流程
SSL通信流程主要有以下四步:
- 加载密钥库,初始化SSLContext;
- 建立Socket连接;
- 进行SSL握手;
- 发送加密应用数据。
一. 初始化SSLContext
二. 建立Socket
服务端
- 创建ServerSocket
服务端使用SSLServerSocketFactoryImpl的createServerSocket方法来创建ServerSocket,该方法主要进行以下两件事:
(1) 调用native方法socketBind绑定服务端地址和端口,调用native方法socketListen进行监听。
(2) 进行SSL信息初始化,获取支持的加密套件和协议版本。
- accept()
(1) 通常我们会在循环中调用accept,accept会事先创建Socket,不过还没初始化;调用native方法socketAccept,该方法会发生阻塞,直到有客户端连接,此时返回初始化完毕的Socket,这里该Socket的实现为SSLSocket。具体代码可以参考ServerSocket的implAccept(Socket)方法。
(2) 当有客户端连接到来时,退出阻塞,返回SSLScoket;接着会初始化ServerHandshaker,初始化主要完成以下操作:
- 将connectionState标记为cs_HANDSHAKE,表示还未经过握手环节。
- 设置支持的协议版本列表、加密套件列表、是否需要客户端认证等。代码参考SSLSocketImpl的initHandshaker方法。
客户端
- 创建Socket
(1) 初始化Session,支持的协议版本、加密套件等;
(2) 使用native方法socketCreate创建Socket;构造URI socket:127.0.0.1:8080
,使用native方法socketConnect发起连接;
(3) 初始化ClientHandshaker,与服务端的类似。
- 客户端连接成功之后,向服务器发送数据,最终会调用SSLSocketImpl的writeRecord方法。
(1) 判断connectionState是否为cs_HANDSHAKE,是的则调用kickstartHandshake开启握手环节,即发送 client_hello,握手环节下面再仔细分析。
三. SSL握手
时序图
流程详解
- 客户端第一次发送数据时,需要进行SSL握手,调用Handshaker的kickstart方法开启握手流程,此时客户端发送ClientHello消息,主要参数如下:
- protocolVersion: 支持的最高协议版本
- sessionId: 会话ID
- cipherSuites: 支持的加密套件
- clnt_random: 客户端随机数
- compression_methods: 压缩算法(可选)
- extensions: 拓展字段,会携带支持的签名算法列表。另外如果支持EC算法,还会携带相关参数。
- 服务端收到客户端的ClientHello之后,会进行以下操作,主要逻辑在ServerHandshaker中的clientHello方法中。
(1) 服务端发送ServerHello消息,主要参数如下:
- protocolVersion: 从服务端支持的协议版本中选取的,该版本小于等于客户端发送过来的协议版本;
- svr_random: 服务端随机数
- sessionId: 会话ID
- cipherSuite: 客户端发送过来的加密套件列表中选取的
- compression_method: 压缩方法
- extensions: 扩展字段
(2) 服务端发送CertificateMsg消息,主要向客户端发送服务端的证书。
(3) 服务端发送ServerKeyExchange消息,ServerKeyExchange是抽象基类,具体子类要看使用哪种密钥交换算法,这里发送的是DH_ServerKeyExchange。
主要参数如下:
- protocolVersion: 协议版本
- dh_p、dh_g、dh_Ys: 前两个是DH算法的参数,后一个是DH算法中的公钥
- signature: 签名
- preferableSignatureAlgorithm: 选中的签名算法
(4) 服务端发送CertificateRequest消息,请求客户端证书。实际上大多数系统只要求单向认证,此步骤通常情况下会被忽略。
(5) 服务端发送ServerHelloDone消息。
-
客户端接收服务端发过来的SererHello消息,主要进行以下处理:设置协商好的协议版本、服务端随机数、加密套件、以及创建Session。
-
客户端接收服务端发过来的CertificateMsg消息,进行证书校验。
-
客户端接收服务端发过来的ServerKeyExchange消息,利用发过来的DH参数(dh_p,dh_p)创建DHCrypt,主要生成客户端的DH公钥,这些参数用于后续生成主密钥。
-
客户端接收服务端发过来的CertificateRequest消息,客户端会构造CertificateMsg消息(certRequest属性,此时还未完全初始化),但此时还不会立即发送给服务端。
-
客户端接收服务端发过来的ServerHelloDone消息,进行以下操作:
(1) 通过判断certRequest是否为空来判断是否需要发送客户端证书,如果需要客户端证书,则从KeyManager中获取客户端证书,构造Certificate握手消息发送给服务端。
(2) 客户端发送ClientKeyExchange握手消息,ClientKeyExchange是抽象基类,具体子类要看使用哪种密钥交换算法,这里发送的是DHClientKeyExchange,主要是客户端的DH公钥。
(3) 使用客户端DH参数和服务端DH公钥生成preMasterSecret,接着用preMasterSecret和prf函数生成masterSecret,session会存储masterSecret;用masterSecret和prf函数生成connectionKeys,connectionKeys包含clntWriteKey,svrWriteKey,clntWriteIV,svrWriteIV,clntMacSecret和svrMacSecret,这些参数用于后续生成通信的readCipher, readAuthenticator, writeCipher, writeAuthenticator。
(4) 客户端发送CertificateVerify握手消息;
(5) 客户端发送contentType为ct_change_cipher_spec的消息,握手消息的contentType为ct_handshake,利用clntWriteKey, clntWriteIV生成客户端的writeCipher,用于客户端读取数据时解密;利用clntMacSecret生成客户端的writeAuthenticator,用于对客户端数据进行签名。
(6) 客户端发送Finished握手消息;
-
服务端接收客户端发的Certificate消息,进行证书校验。
-
服务端接收客户端发的ClientKeyExchange消息,这里收到DHClientKeyExchange消息对象,进行以下操作:
(1) 根据服务端DH参数和客户端DH公钥计算出preMasterSecret;
(2) 利用preMasterSecret和prf函数生成masterSecret,session会存储masterSecret;
(3) 利用masterSecret和prf函数生成connectionKeys,connectionKeys包含clntWriteKey,svrWriteKey,clntWriteIV,svrWriteIV,clntMacSecret和svrMacSecret,这些参数用于后续生成通信的readCipher, readAuthenticator, writeCipher, writeAuthenticator。
-
服务端收到客户端发的CertificateVerify消息,主要对客户端证书进行验签。
-
服务端收到客户端发的change_cipher_spec消息,利用clntWriteKey, clntWriteIV生成服务端的readCipher,readCipher用于服务端读取数据时解密;利用clntMacSecret生成服务端的readAuthenticator,readAuthenticator用于对客户端数据进行验签。
-
服务端收到客户端发的Finished握手消息,进行以下操作:
(1) 对verifyData参数进行验证,用于判断双方生成的masterSecret是否一致。
(2) 发送contentType为ct_change_cipher_spec的消息,利用svrWriteKey和svrWriteIV生成服务端的writeCipher,用于服务端发送数据时进行加密;利用svrMAcSecret生成服务端的writeAuthenticator,用于对服务端的数据进行签名。
(3) 发送Finished握手消息。
-
客户端收到服务端发的change_cipher_spec消息,利用svrWriteKey, svrWriteIV生成客户端的readCipher,用于客户端读取数据时解密;利用svrMacSecret生成客户端的readAuthenticator,用于对服务端数据进行验签。
-
客户端收到服务端发的Finished消息,对verifyData参数进行验证,用于判断双方生成的masterSecret是否一致。
四. 发送应用数据
客户端发送数据时使用writeAuthenticator进行签名,再使用writeCipher对数据进行加密,然后发送给服务端;服务端收到数据后使用readCipher对数据进行解密,再使用readAuthenticator进行验签。服务端发送数据给客户端也是同理。
这篇关于OpenJDK8源码分析SSL握手流程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!