乐趣区

关于golang:码了2000多行代码就是为了讲清楚TLS握手流程续

来自公众号:新世界杂货铺

在“码了 2000 多行代码就是为了讲清楚 TLS 握手流程”这一篇文章的最初挖了一个坑,明天这篇文章就是为了填坑而来,因而本篇次要剖析 TLS1.2 的握手流程。

在写前一篇文章时,笔者的 Demo 只反对解析 TLS1.3 握手流程中发送的音讯,写本篇时,笔者的 Demo 曾经能够解析 TLS1.x 握手流程中的音讯,有趣味的读者请至文末获取 Demo 源码。

论断后行

为保障各位读者对 TLS1.2 的握手流程有一个大略的框架,本篇仍旧论断后行。

单向认证

单向认证客户端不须要证书,客户端验证服务端证书非法即可拜访。

上面是笔者运行 Demo 打印的调试信息:

依据调试信息知,TLS1.2 单向认证中总共收发数据 四次,Client 和 Server 从这四次数据中别离读取不同的信息以达到握手的目标。

笔者将调试信息转换为下述时序图,以不便各位读者了解。

双向认证

双向认证不仅服务端要有证书,客户端也须要证书,只有客户端和服务端证书均非法才可持续拜访(笔者的 Demo 如何开启双向认证请参考前一篇文章中 HTTPS 双向认证局部)。

上面是笔者运行 Demo 打印的调试信息:

同单向认证一样,笔者将调试信息转换为下述时序图。


双向认证和单向认证相比,Server 发消息给 Client 时会额定发送一个 certificateRequestMsg 音讯,Client 收到此音讯后会将证书信息(certificateMsg)和签名信息(certificateVerifyMsg)发送给 Server。

双向认证中,Client 和 Server 发送的音讯变多了,然而总的数据收发依然只有 四次

总结

1、单向认证和双向认证中,总的数据收发仅四次(比 TLS1.3 多一次数据收发),单次发送的数据中蕴含一个或者多个音讯。

2、TLS1.2 中除了 finishedMsg 其余音讯均未加密。

3、在 TLS1.2 中,ChangeCipherSpec音讯之后的所有数据均会做加密解决,它的作用在 TLS1.2 中更像是一个开启加密的开关(TLS1.3 中疏忽此音讯,并不做任何解决)。

和 TLS1.3 的比拟

音讯格局的变动

比照本篇的时序图和前篇的时序图很容易发现局部音讯格局产生了变动。上面是 certificateMsgcertificateMsgTLS13的定义:

// TLS1.2
type certificateMsg struct {raw          []byte
    certificates [][]byte
}
// TLS1.3
type certificateMsgTLS13 struct {raw          []byte
    certificate  tls.Certificate
    ocspStapling bool
    scts         bool
}

其余音讯的定义笔者就不一一列举了,这里仅列出格局发生变化的音讯。

TLS1.2 TLS1.3
certificateRequestMsg certificateRequestMsgTLS13
certificateMsg certificateMsgTLS13

音讯类型的变动

TLS1.2 和 TLS1.3 有雷同的音讯类型也有各自独立的音讯类型。上面是笔者例子中 TLS1.2 和 TLS1.3 各自独有的音讯类型:

TLS1.2 TLS1.3
serverKeyExchangeMsg
clientKeyExchangeMsg
serverHelloDoneMsg
encryptedExtensionsMsg

音讯加密的变动

前篇中提到,TLS1.3 中除了 clientHelloMsgserverHelloMsg其余音讯均做了加密解决,且握手期间和利用数据应用不同的密钥加密。

TLS1.2 中仅有 finishedMsg 做了加密解决,且利用数据也应用该密钥加密。

TLS1.3 会计算两次密钥,Client 和 Server 读取对方的 HelloMsgfinishedMsg之后即可计算密钥。

“Client 和 Server 会各自计算两次密钥,计算机会别离是读取到对方的 HelloMsg 和 finishedMsg 之后”,这是前篇中的形容,计算机会形容不精确以下面为准。

TLS1.2 只计算一次密钥,Client 和 Server 别离收到 serverKeyExchangeMsgclientKeyExchangeMsg之后即可计算密钥,和 TLS1.3 不同的是 TLS1.2 密钥计算后并不会立刻对接下来发送的数据进行加密,只有当发送 / 承受 ChangeCipherSpec 音讯后才会对接下来的数据进行加解密。

生成密钥过程

TLS1.2 和 TLS1.3 生成密钥的过程还是比拟类似的,下图为 Client 读取 serverKeyExchangeMsg 之后的局部解决逻辑:

图中 X25519 是椭圆曲线迪菲 - 赫尔曼(Elliptic-curve Diffie–Hellman,缩写为 ECDH)密钥替换计划之一,这在前篇曾经提到过故本篇不再赘述。

依据 Debug 后果,本例中 ka.preMasterSecret 和 TLS1.3 中的共享密钥生成逻辑完全一致。不仅如此,在后续的代码剖析中,笔者发现 TLS1.2 也应用了 AEAD 加密算法对数据进行加解密(AEAD 在前篇中曾经提到过故本篇不再赘述)。

下图为笔者 Debug 后果:

图中 prefixNonceAEAD 即为 TLS1.2 中 AEAD 加密算法的一种实现。

这里须要留神的是 TLS1.3 也会计算 masterSecret。为了不便了解,咱们先回顾一下 TLS1.3 中生成masterSecret 的局部源码:

// 基于共享密钥派生 hs.handshakeSecret
hs.handshakeSecret = hs.suite.extract(hs.sharedKey,
    hs.suite.deriveSecret(earlySecret, "derived", nil))
// 基于 hs.handshakeSecret 派生 hs.masterSecret
hs.masterSecret = hs.suite.extract(nil,
    hs.suite.deriveSecret(hs.handshakeSecret, "derived", nil))

由上易知,TLS1.3 先通过共享密钥派生出 handshakeSecret,最初通过handshakeSecret 派生出 masterSecret。与此相比,TLS1.2 生成masterSecret 仅需一步:

hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.hello.random, hs.serverHello.random)

masterFromPreMasterSecret函数的作用是利用HMAC(HMAC 在前篇中曾经提到故本篇不再赘述)算法对 Client 和 Server 的随机数以及共享密钥进行摘要,从而计算失去masterSecret

masterSecret在后续的过程中并不会用于数据加密,上面笔者带各位读者别离看一下 TLS1.3 和 TLS1.2 生成数据加密密钥的过程。

TLS1.3 生成数据加密密钥(以 Client 计算 serverSecret 为例):

serverSecret := hs.suite.deriveSecret(hs.masterSecret,
    serverApplicationTrafficLabel, hs.transcript)
c.in.setTrafficSecret(hs.suite, serverSecret)

前篇中提到 hs.suite.deriveSecret 外部会通过 hs.transcript 计算出音讯摘要从而重新得到一个 serverSecretsetTrafficSecret 办法外部会对 serverSecret 计算失去 AEAD 加密算法所须要的 key 和 iv(初始向量:Initialization vector)。

因而可知 TLS1.3 计算密钥和 Client/Server 生成的随机数无间接关系,而与 Client/Server 以后收发的所有音讯的摘要无关。

补充:
IV 通常是随机或者伪随机的。它和数据加密的密钥一起应用能够减少应用字典攻打的攻击者破解明码的难度。例如,如果加密数据中存在反复的序列,则攻击者能够假设音讯中相应的序列也是雷同的,而 IV 就是为了避免密文中呈现相应的反复序列。

参考:

https://whatis.techtarget.com…
https://en.wikipedia.org/wiki…

TLS1.2 生成数据加密密钥:

clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV :=
            keysFromMasterSecret(tr.vers, suite, p.masterSecret, tr.clientHello.random, tr.serverHello.random, suite.macLen, suite.keyLen, suite.ivLen)
serverCipher = hs.suite.aead(serverKey, serverIV)
c.in.prepareCipherSpec(c.vers, serverCipher, serverHash)

前文中提到 masterSecret 的生成与 Client 和 Server 的随机数无关,而通过 keysFromMasterSecret 计算 AEAD 所需的 key 和 iv 仍旧与随机数无关。

小结

1、本例中 TLS1.2 和 TLS1.3 均应用 X25519 算法计算共享密钥。

2、本例中 TLS1.2 和 TLS1.3 均应用 AEAD 进行数据加解密。

3、TLS1.3 通过共享密钥派生两次才失去masterSecret,而 TLS1.2 以共享密钥、Client 和 Server 的随机数一起计算失去masterSecret

4、TLS1.3 通过音讯的摘要再次计算失去一个数据加密密钥,而 TLS1.2 间接通过 masterSecret 计算失去 AEAD 所需的 key 和 iv。

TLS1.1 和 TLS1.0 不反对 HTTP2

在后面提到本文的例子曾经反对解析 TLS1.x 的握手流程,这个时候笔者忽然很好奇浏览器还反对那些版本的 TLS 协定。

而后笔者在谷歌浏览器上首先测试了 TLS1.1 的服务,为了不便测试笔者革新了之前服务器推送的案例:

server := &http.Server{Addr: ":8080", Handler: nil}
server.TLSConfig = new(tls.Config)
server.TLSConfig.PreferServerCipherSuites = true
server.TLSConfig.NextProtos = append(server.TLSConfig.NextProtos, "h2", "http/1.1")
// 服务端反对的最大 tls 版本调整为 1.1
server.TLSConfig.MaxVersion = tls.VersionTLS11
server.ListenAndServeTLS("ca.crt", "ca.key")

运行 Demo 后失去如下截图:

图中红框局部 obsolete 的意思笔者也不知,正好学习一波(技术人的英语大略就是这样缓缓积攒起来的吧)。

这下笔者明确了,TLS1.1 曾经不被反对所以页面才无奈失常拜访,然而事实真是如此嘛?

直到几天后笔者开始写这篇文章时,心田仍是非常纳闷,于是应用了 curl 命令再次拜访。

图中蓝框局部正是 TLS1.1 的握手流程,有趣味的读者能够应用笔者的例子和 curl -v 命令进行双向验证。

图中红框局部提醒说“HTTP2 的数据发送失败”,笔者才豁然开朗,将上述代码作如下微调后页面可失常拜访。

server.TLSConfig.NextProtos = append(server.TLSConfig.NextProtos, "http/1.1")

通过笔者的测试,TLS1.0 同 TLS1.1 一样均不反对 HTTP2 协定,当然这两个协定也不举荐持续应用。

写在最初

“纸上得来终觉浅,绝知此事需躬行”。笔者不敢保障把 TLS 握手流程的每个细节都讲的非常分明,所以倡议各位读者去 github 克隆代码,而后本人一步一步 Debug 必然可能加深印象并彻底了解。当然,顺便关注或者 star 一下这种顺手为之的小事,笔者置信各位读者还是非常乐意的~

最初,衷心希望本文可能对各位读者有肯定的帮忙。

  1. 写本文时,笔者所用 go 版本为: go1.15.2
  2. 文章中所用残缺例子:https://github.com/Isites/go-…
退出移动版