客户端在完成 clientHelloMsg 和 serverHelloMsg 后,开启缓存写入模式,也分为重用 session 和非重用 session 两种情况。服务端在发送完第一批次消息后,等待客户端的回应。
判断如果复用的话. 处理流程为1. 生成相关的Key 2. 读Session Ticket 3. 读Finished 报文 4. 发送缓冲区内容
如果没有复用的话. 处理流程则需要现做完整的握手
// 判断是否复用
if isResume {
if err := hs.establishKeys(); err != nil { return err }
if err := hs.readSessionTicket(); err != nil { return err }
if err := hs.readFinished(c.serverFinished[:]); err != nil { return err }
c.clientFinishedIsFirst = false
if err := hs.sendFinished(c.clientFinished[:]); err != nil { return err }
if _, err := c.flush(); err != nil { return err }
} else {
if err := hs.doFullHandshake(); err != nil { return err }
if err := hs.establishKeys(); err != nil { return err }
if err := hs.sendFinished(c.clientFinished[:]); err != nil { return err }
if _, err := c.flush(); err != nil { return err }
c.clientFinishedIsFirst = true
if err := hs.readSessionTicket(); err != nil { return err }
if err := hs.readFinished(c.serverFinished[:]); err != nil { return err }
}
完成 serverHelloMsg 的处理后,在 fullhandshake 模式下,需要对服务端依次发送的 certificateMsg,certificateStatusMsg(可选),serverKeyExchangeMsg(非 RSA 秘钥交换),certificateRequestMsg(可选),serverHelloDoneMsg 进行处理。代码在./tls/handshake_client.go
中的doFullHandshake()
中。,在Client 做DH密钥交换之前,需先验证服务端证书。主要的函数为在./tls/handshake_client.go
中的
verifyServerCertificate()
读取服务端证书信息certificateMsg,并完成
如果是首次握手,则解析证书,验证证书,保存有效证书到c.peerCertificates;如果不是首次握手,检查消息中的证书与之前存储在c.peerCertificates的证书是否相同
按需读取certificateStatusMsg(可选),并完成,存储证书状态到c.ocspResponse
读取serverKeyExchangeMsg(非 RSA 秘钥交换),完成消息,调用 processServerKeyExchange,从中获取服务端秘钥交换的公钥点,椭圆曲线,以及服务端用其证书私钥对椭圆曲线公钥信息的签名。客户端用服务端证书公钥对此签名进行认证。
读取 certificateRequestMsg(可选),按需获取客户端证书,完成该消息。
读取并完成 serverHelloDoneMsg
如果需要发送客户端证书信息,构造并完成 certificateMsg ,写入 sendBuf,等待推送
调用 generateClientKeyExchange,
如果是 RSA 秘钥交换,生成46字节随机数,加上头两个字节的TLS版本值,就是预备主密钥,用服务端证书公钥对此预备主密钥加密,构造clientKeyExchangeMsg。
如果是 ECDHE 秘钥交换,随机生成客户端秘钥交换私钥,和客户端秘钥交换公钥,构造clientKeyExchangeMsg,生成客户端秘钥交换公钥信息。再根据之前获得的服务端秘钥交换公钥,生成客户端预备主密钥。
完成clientKeyExchangeMsg,写入 sendBuf,等待推送
如果之前发送了客户端的证书,也要发送签名消息 certificateVerifyMsg,签名的对象是 finishedHash 中所有的握手消息(finishedHash.buffer)的 hash 值。完成该消息,写入 sendBuf,等待推送。从这里也就说明了为什么 buffer 只有在需要客户端证书的情况下才不为 nil。
根据预备主密钥,客户端随机数,服务端随机数,计算主密钥
清空 finishedHash 中的 buffer
目前缓存待推送的消息:certificateMsg(可选),clientKeyExchangeMsg,certificateVerifyMsg(可选)
//客户端 fullhandshake
func (hs *clientHandshakeState) doFullHandshake() error {
c := hs.c
// ServerHelloMsg之后,期待收到服务端的证书信息,证书必须存在且不为空
msg, err := c.readHandshake()
if err != nil {
return err
}
certMsg, ok := msg.(*certificateMsg)
if !ok || len(certMsg.certificates) == 0 {
c.sendAlert(alertUnexpectedMessage)
return unexpectedMessageError(certMsg, msg)
}
// fihishedHash 中完成certificateMsg
hs.finishedHash.Write(certMsg.marshal())
if c.handshakes == 0 {
// 如果是初次握手,解析证书
// If this is the first handshake on a connection, process and
// (optionally) verify the server's certificates.
certs := make([]*x509.Certificate, len(certMsg.certificates))
for i, asn1Data := range certMsg.certificates {
cert, err := x509.ParseCertificate(asn1Data)
if err != nil {
c.sendAlert(alertBadCertificate)
return errors.New("tls: failed to parse certificate from server: " + err.Error())
}
certs[i] = cert
}
//如果不是跳过验证,对证书进行验证,并更新 c.verifiedChains,c.peerCertificates
if !c.config.InsecureSkipVerify {
opts := x509.VerifyOptions{
Roots: c.config.RootCAs,
CurrentTime: c.config.time(),
DNSName: c.config.ServerName,
Intermediates: x509.NewCertPool(),
}
for i, cert := range certs {
if i == 0 {
continue
}
opts.Intermediates.AddCert(cert)
}
c.verifiedChains, err = certs[0].Verify(opts)
if err != nil {
c.sendAlert(alertBadCertificate)
return err
}
}
//自定义的验证
if c.config.VerifyPeerCertificate != nil {
if err := c.config.VerifyPeerCertificate(certMsg.certificates, c.verifiedChains); err != nil {
c.sendAlert(alertBadCertificate)
return err
}
}
// 只支持 RSA,ECDSA 的公钥
switch certs[0].PublicKey.(type) {
case *rsa.PublicKey, *ecdsa.PublicKey:
break
default:
c.sendAlert(alertUnsupportedCertificate)
return fmt.Errorf("tls: server's certificate contains an unsupported type of public key: %T", certs[0].PublicKey)
}
//保存解析后的证书
c.peerCertificates = certs
} else {
//不是首次握手,需确保之前存储的服务端的证书没有改变
// This is a renegotiation handshake. We require that the
// server's identity (i.e. leaf certificate) is unchanged and
// thus any previous trust decision is still valid.
//
// See https://mitls.org/pages/attacks/3SHAKE for the
// motivation behind this requirement.
if !bytes.Equal(c.peerCertificates[0].Raw, certMsg.certificates[0]) {
c.sendAlert(alertBadCertificate)
return errors.New("tls: server's identity changed during renegotiation")
}
}
//再次读取 handshake 的 msg,此时有可能是客户端主动要求服务端发送的 certificateStatusMsg
msg, err = c.readHandshake()
if err != nil {
return err
}
cs, ok := msg.(*certificateStatusMsg)
if ok {
// RFC4366 on Certificate Status Request:
// The server MAY return a "certificate_status" message.
//如果服务端ocspStapling标记为假,但收到了certificateStatusMsg,报警
if !hs.serverHello.ocspStapling {
// If a server returns a "CertificateStatus" message, then the
// server MUST have included an extension of type "status_request"
// with empty "extension_data" in the extended server hello.
c.sendAlert(alertUnexpectedMessage)
return errors.New("tls: received unexpected CertificateStatus message")
}
// 累计计算 hash,并置 c.ocspResponse = cs.response
hs.finishedHash.Write(cs.marshal())
if cs.statusType == statusTypeOCSP {
c.ocspResponse = cs.response
}
// 读取下一条 handshake 消息
msg, err = c.readHandshake()
if err != nil {
return err
}
}
// 根据套件获取对应的 keyAgreement
keyAgreement := hs.suite.ka(c.vers)
// 可能是 serverKeyExchangeMsg,如果采用的是 RSA 交换秘钥的模式,没有 serverKeyExchangeMsg
skx, ok := msg.(*serverKeyExchangeMsg)
if ok {
// 如果是 serverKeyExchangeMsg,完成该消息并处理
hs.finishedHash.Write(skx.marshal())
// 从 serverKeyExchangeMsg 获取服务端公钥,并验证签名
err = keyAgreement.processServerKeyExchange(c.config, hs.hello, hs.serverHello, c.peerCertificates[0], skx)
if err != nil {
c.sendAlert(alertUnexpectedMessage)
return err
}
// 读取下一条消息
msg, err = c.readHandshake()
if err != nil {
return err
}
}
var chainToSend *Certificate
var certRequested bool
certReq, ok := msg.(*certificateRequestMsg)
//此时可能是服务端要求客户端发送证书的请求消息
if ok {
certRequested = true
// 完成该消息
hs.finishedHash.Write(certReq.marshal())
// 根据要求获取客户端的证书
if chainToSend, err = hs.getCertificate(certReq); err != nil {
c.sendAlert(alertInternalError)
return err
}
// 读取下一条消息
msg, err = c.readHandshake()
if err != nil {
return err
}
}
// 应该是 HelloDone 消息
shd, ok := msg.(*serverHelloDoneMsg)
if !ok {
c.sendAlert(alertUnexpectedMessage)
return unexpectedMessageError(shd, msg)
}
//] 完成消息
hs.finishedHash.Write(shd.marshal())
// If the server requested a certificate then we have to send a
// Certificate message, even if it's empty because we don't have a
// certificate to send.
// 发送客户端证书信息
if certRequested {
certMsg = new(certificateMsg)
certMsg.certificates = chainToSend.Certificate
hs.finishedHash.Write(certMsg.marshal())
if _, err := c.writeRecord(recordTypeHandshake, certMsg.marshal()); err != nil {
return err
}
}
// 构造客户端秘钥交换消息,由于已经收到服务端的公钥信息,所以同时可以生成预备主密钥了
preMasterSecret, ckx, err := keyAgreement.generateClientKeyExchange(c.config, hs.hello, c.peerCertificates[0])
if err != nil {
c.sendAlert(alertInternalError)
return err
}
// 完成消息,并将 clientKeyExchangeMsg 写入 sendBuf,等待推送
if ckx != nil {
hs.finishedHash.Write(ckx.marshal())
if _, err := c.writeRecord(recordTypeHandshake, ckx.marshal()); err != nil {
return err
}
}
// 如果发送了证书给服务端,那么也要发送签名让服务端验证
if chainToSend != nil && len(chainToSend.Certificate) > 0 {
certVerify := &certificateVerifyMsg{
hasSignatureAndHash: c.vers >= VersionTLS12,
}
//证书支持签名
key, ok := chainToSend.PrivateKey.(crypto.Signer)
if !ok {
c.sendAlert(alertInternalError)
return fmt.Errorf("tls: client certificate private key of type %T does not implement crypto.Signer", chainToSend.PrivateKey)
}
// 证书的签名类型,RSA 或 ECDSA
var signatureType uint8
switch key.Public().(type) {
case *ecdsa.PublicKey:
signatureType = signatureECDSA
case *rsa.PublicKey:
signatureType = signatureRSA
default:
c.sendAlert(alertInternalError)
return fmt.Errorf("tls: failed to sign handshake with client certificate: unknown client certificate key type: %T", key)
}
// SignatureAndHashAlgorithm was introduced in TLS 1.2.
if certVerify.hasSignatureAndHash {
// 以证书的签名类型为基准,获取一种符合服务端要求的签名算法
certVerify.signatureAlgorithm, err = hs.finishedHash.selectClientCertSignatureAlgorithm(certReq.supportedSignatureAlgorithms, signatureType)
if err != nil {
c.sendAlert(alertInternalError)
return err
}
}
// 计算finishedHash.buffer 的 hash值,作为待签名对象
digest, hashFunc, err := hs.finishedHash.hashForClientCertificate(signatureType, certVerify.signatureAlgorithm, hs.masterSecret)
if err != nil {
c.sendAlert(alertInternalError)
return err
}
// 对上述 hash 值以私钥采用同样的 hash 函数进行签名
certVerify.signature, err = key.Sign(c.config.rand(), digest, hashFunc)
if err != nil {
c.sendAlert(alertInternalError)
return err
}
// 累计计算 hash,将 certificateVerifyMsg 写入 conn 缓存
hs.finishedHash.Write(certVerify.marshal())
if _, err := c.writeRecord(recordTypeHandshake, certVerify.marshal()); err != nil {
return err
}
}
// 根据预备主密钥,客户端随机数,服务端随机数,计算主密钥
hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.hello.random, hs.serverHello.random)
// 一般测试用
if err := c.config.writeKeyLog(hs.hello.random, hs.masterSecret); err != nil {
c.sendAlert(alertInternalError)
return errors.New("tls: failed to write to key log: " + err.Error())
}
// 清空 finishedHash 中的 buffer
hs.finishedHash.discardHandshakeBuffer()
return nil
}
1.浏览器向服务器发送随机数 client_random,TLS 版本和供筛选的加密套件列表。
2.服务器接收到,立即返回 server_random,确认好双方都支持的加密套件 以及数字证书 (证书中附带公钥 Public key certificate)。
3.浏览器接收,先验证数字证书。若通过,接着使用加密套件的密钥协商算法 RSA 算法生成另一个随机数 pre_random,并且用证书里的公钥加密,传给服务器。
4.服务器用私钥解密这个被加密后的 pre_random,参考 “非对称加密”。
现在浏览器和服务器都拥有三样相同的凭证:client_random、server_random 和 pre_random。两者都用筛好的加密套件中的加密方法混合这三个随机数,生成最终的密钥。
最后,浏览器和服务器使用相同的密钥进行通信,即使用 对称加密
。
到这里,还有两点需要注意。
第一:握手中的任何消息均未使用秘钥加密,它们都是明文发送的。
第二:TLS 握手其实是一个 双向认证
的过程,客户端和服务器都需要进行摘要认证以及收尾 (发送 Finished 消息,意为后面 HTTP 开始正式加密报文通信了)。
1.浏览器向服务器发送随机数 client_random,TLS 版本和供筛选的加密套件列表。
// RSA
-2.服务器接收到,立即返回 server_random,确认好双方都支持的加密套件 -以及数字证书 (证书中附带公钥)。
// DH
+2.服务器接收到,立即返回 server_random,确认好双方都支持的加密套件 +以及数字证书 (证书中附带公钥)。 +同时服务器利用私钥将 client_random,server_random,server_params 签名, +生成服务器签名。然后将签名和 server_params 也发送给客户端。 +这里的 server_params 为 DH 算法所需参数。
// RSA
-3.浏览器接收,先验证数字证书。 -若通过,接着使用加密套件的密钥协商算法 RSA 算法 -生成另一个随机数 pre_random,并且用证书里的公钥加密,传给服务器。
// DH
+3.浏览器接收,先验证数字证书和 _签名_。 +若通过,将 client_params 传递给服务器。 +这里的 client_params 为 DH 算法所需参数。
-4.服务器用私钥解密这个被加密后的 pre_random,参考 “非对称加密”。
+4.现在客户端和服务器都有 client_params、server_params 两个参数, +因 ECDHE 计算基于 “椭圆曲线离散对数”,通过这两个 DH 参数就能计算出 pre_random。
现在浏览器和服务器都拥有三样相同的凭证:client_random、server_random 和 pre_random,后续步骤与 RSA 握手一致。
DH 握手前向安全性
有了前面一节的概念后,TLS1.2 握手理解起来就显得毫不费力了。因为主流的 TLS1.2 握手就是上节完整的 DH 握手流程。
互联网的世界飞速发展,随着时间的推移,人们早已不满足于现有的 TLS1.2。于是,更快、更安全的 “船新版本” TLS1.3 发布了。
TLS1.3 废除了原有的部分不安全的加密算法,其中甚至包括 RSA
算法。
RSA 算法的废除不仅因为已经有大能将其破解,同时还缺少 前向安全性
。何为前向安全?即能够保护过去进行的通讯不受密码或密钥在未来暴露的威胁。
现在我们来看看 TLS1.3 握手具体流程,先放图:
// 原 DH 握手
-1.浏览器向服务器发送 client_random,TLS 版本和供筛选的加密套件列表。
// TLS1.3 优化
+1.浏览器向服务器发送 client_params,client_random,TLS 版本和供筛选的加密套件列表。
// 原 DH 握手
-2...
// TLS1.3 优化
+2.服务器返回:server_random、server_params、TLS 版本、确定的加密套件方法以及证书。 +浏览器接收,先验证数字证书和签名。
最后,集齐三个参数,生成最终秘钥。
如你所见,TLS1.3 客户端和服务器之间只需要一次往返就完成 (TLS1.2 需要两次往返来完成握手),即 1-RTT
握手。当然,如果利用 PSK
我们甚至能优化到 0-RTT
(这并不好,安全受到质疑~)。
以上,我们梳理了三个版本的 TLS 握手流程,它们其实属于两类:一种基于 RSA,另一种基于 Diffie-Hellman (ECDHE)。这两类握手的区别仅在于如何实现秘钥建立和身份验证:
秘钥交换 | 身份验证 | |
---|---|---|
RSA 握手 | RSA | RSA |
DH 握手 | ECDHE | RSA/DSA |
我们看到,在版本演进中,最新的 TLS1.3 抛弃了 RSA 算法是为了更安全,减少 RTT 是为了更快。可知,互联网技术革新的特点不外如是。
参考链接:https://juejin.cn/post/6895624327896432654
https://kiosk007.top/post/tls%E8%AF%A6%E8%A7%A3-%E4%BA%8C/#client-key-exchange
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。