码了2000多行代码就是为了讲清楚TLS握手流程

{"type":"doc","content":[{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"来自公众号:新世界杂货铺","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"前言","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"呼,这篇文章的准备周期可谓是相当的长了!原本是想直接通过源码进行分析的,但是发现TLS握手流程调试起来非常不方便,笔者怒了,于是实现了一个极简的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"net.Conn","attrs":{}}],"attrs":{}},{"type":"text","text":"接口以方便调试。码着码着,笔者哭了,因为现在这个调试Demo已经达到2000多行代码了!","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/be/be39ad48832a087d5fa3438b7403d43f.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"虽然码了两千多行代码,但是目前只能够解析TLS1.3握手流程中发送的消息,因此本篇主要分析TLS1.3的握手流程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"特别提醒:有想在本地调试一番的小伙伴请至文末获取本篇源码。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"结论先行","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"鉴于本文篇幅较长,笔者决定结论先行,以助各位读者理解后文详细的分析内容。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"HTTPS单向认证","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"单向认证客户端不需要证书,客户端只要验证服务端证书合法即可访问。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面是笔者运行Demo打印的调试信息:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8c/8cd8c2688e51b3f497477830ca2b88f2.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根据调试信息知,在TLS1.3单向认证中,总共收发数据","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"三次","attrs":{}},{"type":"text","text":",Client和Server从这三次数据中分别读取不同的信息以达到握手的目的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"注意","attrs":{}},{"type":"text","text":":TLS1.3不处理","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ChangeCipherSpec","attrs":{}}],"attrs":{}},{"type":"text","text":"类型的数据,而该数据在TLS1.2中是需要处理的。因本篇主要分析TLS1.3握手流程,故后续不会再提及","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ChangeCipherSpec","attrs":{}}],"attrs":{}},{"type":"text","text":",同时","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"时序图中也会忽略此消息","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"笔者将调试信息转换为下述时序图,以方便各位读者理解。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/3c/3cfe4fd7fce54a79e2a5e1882b1b995a.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"HTTPS双向认证","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"双向认证不仅服务端要有证书,客户端也需要证书,只有客户端和服务端证书均合法才可继续访问。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"笔者在这里特别提醒,开启双向认证很简单,在笔者的Demo中取消下面代码的注释即可。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"// sconf.ClientAuth = tls.RequireAndVerifyClientCert","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外,笔者在","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"main.go","attrs":{}}],"attrs":{}},{"type":"text","text":"同目录下留有测试用的根证书、服务端证书和客户端证书,为了保证双向认证的顺利运行请将根证书安装为受用户信任的证书。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面是笔者运行Demo打印的调试信息:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/53/5365946d1ccbdb044546fb3e8074ef50.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同单向认证一样,笔者将调试信息转换为下述时序图。 ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ea/eaa29576d95429f7be4e0d74756acf63.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"双向认证和单向认证相比,Server发消息给Client时会额外发送一个","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateRequestMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"消息,Client收到此消息后会将证书信息(","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":")和签名信息(","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateVerifyMsg","attrs":{}}],"attrs":{}},{"type":"text","text":")发送给Server。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"双向认证中,Client和Server发送消息变多了,但是总的数据收发仍然只有","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"三次","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"总结","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、TLS1.3和TLS1.2握手流程是有区别的,这一点需要注意。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、单向认证和双向认证中,总的数据收发仅三次,单次发送的数据中包含一个或者多个消息。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"clientHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"未经过加密,之后发送的消息均做了加密处理。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4、Client和Server会各自计算两次密钥,计算时机分别是读取到对方的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"HelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"之后。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"注","attrs":{}},{"type":"text","text":":上述第3点和第4点分析过程详见后文。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Client发送HelloMsg","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在TLS握手过程中的第一步是Client发送HelloMsg,所以针对TLS握手流程的分析也从这一步开始。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Server对于Client的基本信息了解完全依赖于Client主动告知Server,而其中比较关键的信息分别是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"客户端支持的TLS版本","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"客户端支持的加密套件(cipherSuites)","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"客户端支持的签名算法","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"客户端支持的密钥交换协议以及其对应的公钥","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"客户端支持的TLS版本:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客户端支持的TLS版本主要通过tls包中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"(*Config).supportedVersions","attrs":{}}],"attrs":{}},{"type":"text","text":"方法计算。对TLS1.3来说默认支持的TLS版本如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"var supportedVersions = []uint16{\n\tVersionTLS13,\n\tVersionTLS12,\n\tVersionTLS11,\n\tVersionTLS10,\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在发起请求时如果用户手动设置了","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"tls.Config","attrs":{}}],"attrs":{}},{"type":"text","text":"中的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"MaxVersion","attrs":{}}],"attrs":{}},{"type":"text","text":"或者","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"MinVersion","attrs":{}}],"attrs":{}},{"type":"text","text":",则客户端支持的TLS版本会发生变化。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"例如发起请求时,设置了","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"conf.MaxVersion = tls.VersionTLS12","attrs":{}}],"attrs":{}},{"type":"text","text":",此时","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"(*Config).supportedVersions","attrs":{}}],"attrs":{}},{"type":"text","text":"返回的版本为:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"[]uint16{\n\tVersionTLS12,\n\tVersionTLS11,\n\tVersionTLS10,\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ps: 如果有兴趣的小伙伴可以在克隆笔者的demo后手动设置Config.MaxVersion,设置后可以调试TLS1.2的握手流程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"客户端支持的加密套件(cipherSuites):","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"说实话,加密套件已经进入笔者的知识盲区了,其作用笔者会在下一小节讲明白,故本小节笔者直接贴出计算后的结果。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/eb/eb7d21a5652e847b40bfc868cc0c7d3d.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"图中篮框部分为当前Client支持加密套件Id,红框部分为计算逻辑。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"客户端支持的签名算法:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客户端支持的签名算法,仅在客户端支持的最大TLS版本大于等于TLS1.2时生效。此时客户端支持的签名算法如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"var supportedSignatureAlgorithms = []SignatureScheme{\n\tPSSWithSHA256,\n\tECDSAWithP256AndSHA256,\n\tEd25519,\n\tPSSWithSHA384,\n\tPSSWithSHA512,\n\tPKCS1WithSHA256,\n\tPKCS1WithSHA384,\n\tPKCS1WithSHA512,\n\tECDSAWithP384AndSHA384,\n\tECDSAWithP521AndSHA512,\n\tPKCS1WithSHA1,\n\tECDSAWithSHA1,\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"客户端支持的密钥交换协议及其对应的公钥:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这一块儿逻辑仅在客户端支持的最大TLS版本是TLS1.3时生效。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"if hello.supportedVersions[0] == VersionTLS13 {\n\thello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13()...)\n\n\tcurveID := config.curvePreferences()[0]\n\tif _, ok := curveForCurveID(curveID); curveID != X25519 && !ok {\n\t\treturn nil, nil, errors.New(\"tls: CurvePreferences includes unsupported curve\")\n\t}\n\tparams, err = generateECDHEParameters(config.rand(), curveID)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\thello.keyShares = []keyShare{{group: curveID, data: params.PublicKey()}}\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述代码中,方法","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"config.curvePreferences","attrs":{}}],"attrs":{}},{"type":"text","text":"的逻辑为:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"var defaultCurvePreferences = []CurveID{X25519, CurveP256, CurveP384, CurveP521}\nfunc (c *Config) curvePreferences() []CurveID {\n\tif c == nil || len(c.CurvePreferences) == 0 {\n\t\treturn defaultCurvePreferences\n\t}\n\treturn c.CurvePreferences\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在本篇中,笔者未手动设置优先可供选择的曲线,故","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"curveID","attrs":{}}],"attrs":{}},{"type":"text","text":"的值为","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"X25519","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述代码中,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"generateECDHEParameters","attrs":{}}],"attrs":{}},{"type":"text","text":"函数的作用是根据曲线Id生成一种椭圆曲线密钥交换协议的实现。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果客户端支持的最大TLS版本是TLS1.3时,会为Client支持的加密套件增加TLS1.3默认的加密套件,同时还会选择","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Curve25519","attrs":{}},{"type":"text","text":"密钥交换协议生成","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"keyShare","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"小结","attrs":{}},{"type":"text","text":":本节介绍了在TLS1.3中Client需要告知Server客户端支持的TLS版本号、客户端支持的加密套件、客户端支持的签名算法和客户端支持的密钥交换协议。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Server读HelloMsg&发送消息","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Server读到","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"clientHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"之后会根据客户端支持的TLS版本和本地支持的TLS版本做对比,得到Client和Server均支持的TLS版本最大值,该值作为后续继续通信的标准。本篇中Client和Server都支持TLS1.3,因此Server进入TLS1.3的握手流程。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"处理clientHelloMsg","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Server进入TLS1.3握手流程之后,还需要继续处理clientHelloMsg,同时构建","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Server支持的TLS版本","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"进入TLS1.3握手流程之前,Server已经计算出两端均支持的TLS版本,但是Client还无法得知Server支持的TLS版本,因此开始继续处理clientHelloMsg时,Server将已经计算得到的TLS版本赋值给","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"supportedVersion","attrs":{}}],"attrs":{}},{"type":"text","text":"以告知客户端。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"// client读取到serverHelloMsg后,通过读取此字段计算两端均支持的TLS版本\nhs.hello.supportedVersion = c.vers","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Server计算两端均支持的加密套件","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"clientHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"中含有Client支持的加密套件信息,Server读取该信息并和本地支持的加密套件做对比计算出两端均支持的加密套件。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这里需要注意的是,如果Server的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"tls.Config.PreferServerCipherSuites","attrs":{}}],"attrs":{}},{"type":"text","text":"为","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"true","attrs":{}}],"attrs":{}},{"type":"text","text":"则选择Server第一个在两端均支持的加密套件,否则选择Client第一个在两端均支持的加密套件。笔者通过Debug得到两端均支持的加密套件id为","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"4865","attrs":{}}],"attrs":{}},{"type":"text","text":"(其常量为","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"tls.TLS","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"AES","attrs":{}},{"type":"text","text":"128_GCM_SHA256","attrs":{}}],"attrs":{}},{"type":"text","text":"),详情见下图:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/31/31924f987ea466b2cac89138ec90e483.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上图中的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mutualCipherSuiteTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"函数会从","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cipherSuitesTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"变量中选择匹配的加密套件。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"var cipherSuitesTLS13 = []*cipherSuiteTLS13{\n\t{TLS_AES_128_GCM_SHA256, 16, aeadAESGCMTLS13, crypto.SHA256},\n\t{TLS_CHACHA20_POLY1305_SHA256, 32, aeadChaCha20Poly1305, crypto.SHA256},\n\t{TLS_AES_256_GCM_SHA384, 32, aeadAESGCMTLS13, crypto.SHA384},\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"结合前面的Debug信息知,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.suite","attrs":{}}],"attrs":{}},{"type":"text","text":"为","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cipherSuiteTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"结构体的变量且其值为","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cipherSuitesTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"切片的第一个。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cipherSuiteTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"结构体定义如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"type cipherSuiteTLS13 struct {\n\tid uint16\n\tkeyLen int\n\taead func(key, fixedNonce []byte) aead\n\thash crypto.Hash\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至此,Server已经计算出双端均支持的加密套件,Server通过设置","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cipherSuite","attrs":{}}],"attrs":{}},{"type":"text","text":"将双端均支持的加密套件告知Client:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"hs.hello.cipherSuite = hs.suite.id\nhs.transcript = hs.suite.hash.New()","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在后续计算密钥时需要对Client和Server之间的所有消息计算Hash摘要。根据前面计算出的加密套件知,本篇中计算消息摘要的Hash算法为","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"SHA256","attrs":{}}],"attrs":{}},{"type":"text","text":",此算法的实现赋值给","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.transcript","attrs":{}}],"attrs":{}},{"type":"text","text":"变量,后续计算消息摘要时均通过该变量实现。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Server计算双端均支持的密钥交换协议以及对应的公钥","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"clientHelloMsg.keyShares","attrs":{}}],"attrs":{}},{"type":"text","text":"变量记录着Client支持的曲线Id以及对应的公钥。Server通过对比本地支持的曲线Id计算出双端均支持的密钥交换协议。根据前面","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Client发送HelloMsg","attrs":{}},{"type":"text","text":"这一小节的内容以及笔者实际调试的结果,双端均支持的曲线为","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Curve25519","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Server计算出双端均支持的曲线后,调用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"generateECDHEParameters","attrs":{}}],"attrs":{}},{"type":"text","text":"方法得到对应密钥交换协议的实现,即Curve25519密钥交换协议。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Curve25519","attrs":{}}],"attrs":{}},{"type":"text","text":"是椭圆曲线迪菲-赫尔曼(Elliptic-curve Diffie–Hellman ,缩写为ECDH)密钥交换方案之一,同时也是最快的ECC(Elliptic-curve cryptography)曲线之一。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"ECDH","attrs":{}}],"attrs":{}},{"type":"text","text":"可以为Client和Server在不安全的通道上为双方建立共享密钥,并且Client和Server需要各自持有一组椭圆曲线公私密钥对。当Client和Server需要建立共享密钥时仅需要公布各自的公钥,Client和Server通过对方的公钥以及自己的私钥即可计算出相等的密钥。如果公钥被第三方截获也无关紧要,因为第三方没有私钥无法计算出共享密钥除非第三方能够解决椭圆曲线Diffie–Hellman问题。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ECDHE","attrs":{}}],"attrs":{}},{"type":"text","text":"为","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ECDH","attrs":{}}],"attrs":{}},{"type":"text","text":"的一个变种,其区别仅仅是私钥和公钥在每次建立共享密钥时均需重新生成(以上为笔者对维基百科中ECDH的理解)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ECDHE","attrs":{}}],"attrs":{}},{"type":"text","text":"有了一定的理解后,我们现在看一下","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"generateECDHEParameters","attrs":{}}],"attrs":{}},{"type":"text","text":"函数中的部分源码:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func generateECDHEParameters(rand io.Reader, curveID CurveID) (ecdheParameters, error) {\n\tif curveID == X25519 {\n\t\tprivateKey := make([]byte, curve25519.ScalarSize)\n\t\tif _, err := io.ReadFull(rand, privateKey); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tpublicKey, err := curve25519.X25519(privateKey, curve25519.Basepoint)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &x25519Parameters{privateKey: privateKey, publicKey: publicKey}, nil\n\t}\n // 此处省略代码\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每次调用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"generateECDHEParameters","attrs":{}}],"attrs":{}},{"type":"text","text":"函数时均会生成一组新的椭圆曲线公私密钥对。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"clientHelloMsg.keyShares","attrs":{}}],"attrs":{}},{"type":"text","text":"变量存有Client的公钥,因此Server已经可以计算共享密钥:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"params, err := generateECDHEParameters(c.config.rand(), selectedGroup)\nif err != nil {\n c.sendAlert(alertInternalError)\n return err\n}\nhs.hello.serverShare = keyShare{group: selectedGroup, data: params.PublicKey()}\nhs.sharedKey = params.SharedKey(clientKeyShare.data) // 共享密钥","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述代码中Server已经计算出共享密钥,之后可以通过此密钥派生出其他密钥为数据加密。Client因为无Server的公钥还无法计算出共享密钥,所以Server通过设置","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverShare","attrs":{}}],"attrs":{}},{"type":"text","text":"变量告知Client服务端的公钥。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至此,Server对Client发来的helloMsg已经处理完毕。笔者在这里额外提醒一句,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"clientHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"中仍然有Client和Server生成的随机数,但是在TLS1.3中这两个随机数已经和密钥交换无关了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"小结","attrs":{}},{"type":"text","text":":本节介绍了Server读取","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"clientHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"后会计算双端支持的TLS版本以及双端支持的加密套件和密钥交换协议,同时还介绍了共享密钥的生成以及ECDH的概念。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"选择合适的证书以及签名算法","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在Server选择和当前Client匹配的证书前其实还有关于预共享密钥模式的处理,该模式需要实现","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ClientSessionCache","attrs":{}}],"attrs":{}},{"type":"text","text":"接口,鉴于其不影响握手流程的分析,故本篇不讨论预共享密钥模式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一个Server可能给多个Host提供服务,因此Server可能持有多个证书,那么选择一个和当前Client匹配的证书是十分必要的,其实现逻辑参见","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"(*Config).getCertificate","attrs":{}}],"attrs":{}},{"type":"text","text":"方法。本篇中的Demo只有一个证书,故该方法会直接返回此证书。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"证书中是包含公钥的,不同的公钥支持的签名算法是不同的,在本例中Server支持的签名算法和最终双端均支持的签名算法见下面的Debug结果:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/bb/bbd67e99ae3eff7fd050fc3d02e696ec.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上图中红框部分为Server支持的签名算法,蓝框为选定的双端均支持的签名算法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"小结","attrs":{}},{"type":"text","text":":本节主要介绍了Server选择匹配当前Client的证书和签名算法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"计算握手阶段的密钥以及发送Server的参数","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在这个阶段Server会将","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"写入缓冲区,写完之后再写入一个","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ChangeCipherSpec","attrs":{}}],"attrs":{}},{"type":"text","text":"(TLS1.3不会处理此消息)消息,需要注意的是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"未进行加密发送。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"计算握手阶段的密钥","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前面提到过计算密钥需要计算消息摘要:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"hs.transcript.Write(hs.clientHello.marshal())\nhs.transcript.Write(hs.hello.marshal()) // hs.hello为serverHelloMsg","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述代码中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.transcript","attrs":{}}],"attrs":{}},{"type":"text","text":"在前面已经提到过是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"SHA256","attrs":{}}],"attrs":{}},{"type":"text","text":"Hash算法的一种实现。下面我们逐步分析源码中Server第一次计算密钥的过程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,派生出","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"handshakeSecret","attrs":{}}],"attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"earlySecret := hs.earlySecret \nif earlySecret == nil {\n earlySecret = hs.suite.extract(nil, nil)\n}\nhs.handshakeSecret = hs.suite.extract(hs.sharedKey, \nhs.suite.deriveSecret(earlySecret, \"derived\", nil))","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"earlySecret","attrs":{}}],"attrs":{}},{"type":"text","text":"和预共享密钥有关,因本篇不涉及预共享密钥,故","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"earlySecret","attrs":{}}],"attrs":{}},{"type":"text","text":"为","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"nil","attrs":{}}],"attrs":{}},{"type":"text","text":"。此时,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"earlySecret","attrs":{}}],"attrs":{}},{"type":"text","text":"会通过加密套件派生出一个密钥。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"// extract implements HKDF-Extract with the cipher suite hash.\nfunc (c *cipherSuiteTLS13) extract(newSecret, currentSecret []byte) []byte {\n\tif newSecret == nil {\n\t\tnewSecret = make([]byte, c.hash.Size())\n\t}\n\treturn hkdf.Extract(c.hash.New, newSecret, currentSecret)\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述代码中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"HDKF","attrs":{}}],"attrs":{}},{"type":"text","text":"是一种基于哈希消息身份验证的密钥派生算法,其两个主要用途分别为:一、从较大的随机源中提取更加均匀和随机的密钥;二、将已经合理的随机输入(例如共享密钥)扩展为更大的密码独立输出,从而将共享密钥派生出多个密钥(以上为笔者对维基百科中HKDF的理解)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述代码中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.suite.deriveSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"方法笔者就不列出其源码了,该方法最终会调用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hkdf.Expand","attrs":{}}],"attrs":{}},{"type":"text","text":"方法进行密钥派生。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此时再次回顾","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.handshakeSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"的生成正是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"HKDF","attrs":{}}],"attrs":{}},{"type":"text","text":"算法基于","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"sharedKey","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"earlySecret","attrs":{}}],"attrs":{}},{"type":"text","text":"计算的结果。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然后,通过","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"handshakeSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"和消息摘要派生出一组密钥。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"clientSecret := hs.suite.deriveSecret(hs.handshakeSecret,\n\tclientHandshakeTrafficLabel, hs.transcript)\nc.in.setTrafficSecret(hs.suite, clientSecret)\nserverSecret := hs.suite.deriveSecret(hs.handshakeSecret,\n\tserverHandshakeTrafficLabel, hs.transcript)\nc.out.setTrafficSecret(hs.suite, serverSecret)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述代码中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"clientHandshakeTrafficLabel","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverHandshakeTrafficLabel","attrs":{}}],"attrs":{}},{"type":"text","text":"为常量,其值分别为","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"c hs traffic","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"s hs traffic","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.suite.deriveSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"方法会在内部调用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.transcript.Sum(nil)","attrs":{}}],"attrs":{}},{"type":"text","text":"计算出消息的摘要信息,所以","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"clientSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"HKDF","attrs":{}}],"attrs":{}},{"type":"text","text":"算法基于","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"handshakeSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"和两个常量以及Server和Client已经发送的消息的摘要派生出的密钥。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"clientSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"在服务端用于对收到的数据进行解密,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"在服务端对要发送的数据进行加密。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"c.in","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"c.out","attrs":{}}],"attrs":{}},{"type":"text","text":"同其语义一样,分别用于处理收到的数据和要发送的数据。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面看看笔者对","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"setTrafficSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"方法的Debug结果:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d7/d7599620f1308ba7ab3d087cdc79a6b8.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上图中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"trafficKey","attrs":{}}],"attrs":{}},{"type":"text","text":"方法使用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"HKDF","attrs":{}}],"attrs":{}},{"type":"text","text":"算法对密钥进行了再次派生,笔者就不再对其展开。这里需要关注的是红框部分,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"aes-gcm","attrs":{}}],"attrs":{}},{"type":"text","text":"是一种","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AEAD","attrs":{}}],"attrs":{}},{"type":"text","text":"加密。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"单纯的对称加密算法,其解密步骤是无法确认密钥是否正确的。也就是说,加密后的数据可以用任何密钥执行解密运算,得到一组疑似原始数据,然而并不知道密钥是否是正确,也不知道解密出来的原始数据是否正确。因此,需要在单纯的加密算法之上,加上一层验证手段,来确认解密步骤是否正确,这就是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AEAD","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至此,Server在握手阶段的密钥生成结束,此阶段之后发送的消息(即","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ChangeCipherSpec","attrs":{}}],"attrs":{}},{"type":"text","text":"之后的消息),均通过","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"aes-gcm","attrs":{}}],"attrs":{}},{"type":"text","text":"算法加密。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后回顾一下加密套件的作用:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、提供消息摘要的Hash算法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、提供加解密的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AEAD","attrs":{}}],"attrs":{}},{"type":"text","text":"算法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后再顺便提一嘴,笔者Demo中parse.go文件的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"processMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"方法在处理","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"时有计算握手阶段密钥的极简实现。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"支持的HTTP协议","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Client通过","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"clientHelloMsg.alpnProtocols","attrs":{}}],"attrs":{}},{"type":"text","text":"告知Server客户端支持的HTTP协议,Server通过对比本地支持的HTTP协议,最终选择双端均支持的协议并构建","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"encryptedExtensionsMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"消息告知Client","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"encryptedExtensions := new(encryptedExtensionsMsg)\nif len(hs.clientHello.alpnProtocols) > 0 {\n if selectedProto, fallback := mutualProtocol(hs.clientHello.alpnProtocols, c.config.NextProtos); !fallback {\n encryptedExtensions.alpnProtocol = selectedProto\n c.clientProtocol = selectedProto\n }\n}\nhs.transcript.Write(encryptedExtensions.marshal())","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"hs.clientHello.alpnProtocols","attrs":{}}],"attrs":{}},{"type":"text","text":"的数据来源为客户端的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"tls.Config.NextProtos","attrs":{}}],"attrs":{}},{"type":"text","text":"。在笔者的Demo中,Client和Server均支持","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"h2","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"http1.1","attrs":{}}],"attrs":{}},{"type":"text","text":"这两种协议。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这里顺便强调一下,Client或者Server在获取到对方的helloMsg之后接受/发送的消息均会调用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.transcript.Write","attrs":{}}],"attrs":{}},{"type":"text","text":"方法,以便计算密钥时可以快速计算消息摘要。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"小结","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、本节讨论了握手阶段的密钥生成流程:对消息摘要,然后用HKDF算法对共享密钥和消息摘要派生密钥,最后通过加密套件返回AEAD算法的实现。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、确认了加密套件的作用。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、计算两端均支持的HTTP协议。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"发送Server证书以及签名","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此阶段主要涉及三个消息,分别是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateRequestMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateVerifyMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateRequestMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"仅在双向认证时才发送给Client,单向认证时Server不发送此消息。这里也再次印证了前面单向认证和双向认证时序图中Server发送的消息数量不一致的原因。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"certificateMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"消息的主体是Server的证书这个没什么好说的,下面着重分析一下","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateVerifyMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"私钥签名","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,构建","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateVerifyMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"并设置其签名算法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"certVerifyMsg := new(certificateVerifyMsg)\ncertVerifyMsg.hasSignatureAlgorithm = true // 没有签名算法无法签名,所以直接写true没毛病\ncertVerifyMsg.signatureAlgorithm = hs.sigAlg","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述代码中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.sigAlg","attrs":{}}],"attrs":{}},{"type":"text","text":"为","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"选择合适的证书以及签名算法","attrs":{}},{"type":"text","text":"小节选择的签名算法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然后,通过签名算法计算签名类型以及签名hash,并构建签名选项。以下为笔者Debug结果:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/49/498ab0908e3c2978543cefe1a1ed4a2d.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由上图知,签名类型为","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"signatureRSAPSS","attrs":{}}],"attrs":{}},{"type":"text","text":",签名哈希算法为","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"SHA256","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"signedMessage","attrs":{}}],"attrs":{}},{"type":"text","text":"的作用是将消息的摘要和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverSignatureContext","attrs":{}}],"attrs":{}},{"type":"text","text":"(值为","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"TLS 1.3, server CertificateVerify\\x00","attrs":{}}],"attrs":{}},{"type":"text","text":")常量按照固定格式构建为待签名数据。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后,计算签名并发送消息。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"sig, err := hs.cert.PrivateKey.(crypto.Signer).Sign(c.config.rand(), signed, signOpts)\nif err != nil {\n // 省略代码\n return errors.New(\"tls: failed to sign handshake: \" + err.Error())\n}\ncertVerifyMsg.signature = sig\nhs.transcript.Write(certVerifyMsg.marshal())","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"特别提醒,私钥加密公钥解密称之为签名。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"小结","attrs":{}},{"type":"text","text":":本节主要介绍了此阶段会发送的三种消息,以及Server签名的过程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"发送finishedMsg并再次计算密钥","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"发送finishedMsg","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"finishedMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"的内容非常简单,仅一个字段:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"finished := &finishedMsg{\n verifyData: hs.suite.finishedHash(c.out.trafficSecret, hs.transcript),\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"verifyData","attrs":{}}],"attrs":{}},{"type":"text","text":"通过加密套件的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedHash","attrs":{}}],"attrs":{}},{"type":"text","text":"计算得出,下面我们看看","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedHash","attrs":{}}],"attrs":{}},{"type":"text","text":"的内容:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func (c *cipherSuiteTLS13) finishedHash(baseKey []byte, transcript hash.Hash) []byte {\n\tfinishedKey := c.expandLabel(baseKey, \"finished\", nil, c.hash.Size())\n\tverifyData := hmac.New(c.hash.New, finishedKey)\n\tverifyData.Write(transcript.Sum(nil))\n\treturn verifyData.Sum(nil)\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"HMAC","attrs":{}}],"attrs":{}},{"type":"text","text":"是一种利用密码学中的散列函数来进行消息认证的一种机制,所能提供的消息认证包括两方面内容(此内容摘自百度百科):","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"消息完整性认证:能够证明消息内容在传送过程没有被修改。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"信源身份认证:因为通信双方共享了认证的密钥,接收方能够认证发送该数据的信源与所宣称的一致,即能够可靠地确认接收的消息与发送的一致。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述代码中,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"c.expandLabel","attrs":{}}],"attrs":{}},{"type":"text","text":"最种会调用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hkdf.Expand","attrs":{}}],"attrs":{}},{"type":"text","text":"派生出新的密钥。最后用新的密钥以及消息摘要通过","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"HMAC","attrs":{}}],"attrs":{}},{"type":"text","text":"算法计算出","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"verifyData","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"收到","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"一方通过同样的方式在本地计算出","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"verifyData'","attrs":{}}],"attrs":{}},{"type":"text","text":",如果","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"verifyData'","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"verifyData","attrs":{}}],"attrs":{}},{"type":"text","text":"相等,则证明此消息未被修改且来源可信。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"再次计算密钥","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本次计算密钥的过程和前面计算密钥的流程相似,所以直接上代码:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"hs.masterSecret = hs.suite.extract(nil,\n\ths.suite.deriveSecret(hs.handshakeSecret, \"derived\", nil))\n\nhs.trafficSecret = hs.suite.deriveSecret(hs.masterSecret,\n\tclientApplicationTrafficLabel, hs.transcript)\nserverSecret := hs.suite.deriveSecret(hs.masterSecret,\n\tserverApplicationTrafficLabel, hs.transcript)\nc.out.setTrafficSecret(hs.suite, serverSecret)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,利用前文已经生成的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"handshakeSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"\t再次派生出","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"masterSecret","attrs":{}}],"attrs":{}},{"type":"text","text":",然后再从","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"masterSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"派生出","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"trafficSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverSecret","attrs":{}}],"attrs":{}},{"type":"text","text":",最后调用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"c.out.setTrafficSecret(hs.suite, serverSecret)","attrs":{}}],"attrs":{}},{"type":"text","text":"计算出Server发送数据时的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AEAD","attrs":{}}],"attrs":{}},{"type":"text","text":"加密算法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需要注意的是,此时利用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"生成的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AEAD","attrs":{}}],"attrs":{}},{"type":"text","text":"加密算法会用于握手结束后对要发送的业务数据进行加密。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此阶段结束后,Server会调用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"c.flush()","attrs":{}}],"attrs":{}},{"type":"text","text":"方法,将前面提到的消息一次性发送给Client。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"小结","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、本节介绍了","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"的生成过程,其中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedMsg.verifyData","attrs":{}}],"attrs":{}},{"type":"text","text":"通过","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"HMAC","attrs":{}}],"attrs":{}},{"type":"text","text":"算法计算得出。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"的作用是确保握手过程中发送的消息未被篡改,且数据来源可信。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、计算Server发送业务数据时的加密密钥。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Client读消息&发送消息","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Client读到","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"之后会读取服务端支持的TLS版本并和本地支持的版本做对比,前文已经提到过服务端支持的TLS版本是TLS1.3,因此Client也进入TLS1.3握手流程。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"读取serverHelloMsg并计算密钥","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Client进入TLS1.3握手流程后,有一系列的检查逻辑,这些逻辑比较长而且笔者也不需要考虑这些异常,因此笔者化繁为简,在下面列出关键逻辑:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"selectedSuite := mutualCipherSuiteTLS13(hs.hello.cipherSuites,\n\ths.serverHello.cipherSuite) // 结合Server支持的加密套件选择双端均支持的加密套件\nhs.suite = selectedSuite\nhs.transcript = hs.suite.hash.New()\nhs.transcript.Write(hs.hello.marshal()) // hs.hello为clientHelloMsg\nhs.transcript.Write(hs.serverHello.marshal())","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面这一段代码逻辑和Server处理加密套件以及通过加密套件构建消息摘要算法的实现逻辑相对应,因此笔者不再过多赘述。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面我们看一下计算握手阶段的密钥以及","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"masterSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"的生成:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"sharedKey := hs.ecdheParams.SharedKey(hs.serverHello.serverShare.data)\nearlySecret := hs.earlySecret\nif !hs.usingPSK {\n earlySecret = hs.suite.extract(nil, nil)\n}\nhandshakeSecret := hs.suite.extract(sharedKey,\n\ths.suite.deriveSecret(earlySecret, \"derived\", nil)) // 通过共享密钥派生出handshakeSecret\n\nclientSecret := hs.suite.deriveSecret(handshakeSecret,\n\tclientHandshakeTrafficLabel, hs.transcript) // 通过handshakeSecret派生出clientSecret\nc.out.setTrafficSecret(hs.suite, clientSecret)\nserverSecret := hs.suite.deriveSecret(handshakeSecret,\n\tserverHandshakeTrafficLabel, hs.transcript) // 通过handshakeSecret派生出serverSecret\nc.in.setTrafficSecret(hs.suite, serverSecret)\nhs.masterSecret = hs.suite.extract(nil,\n\ths.suite.deriveSecret(handshakeSecret, \"derived\", nil)) // 通过handshakeSecret派生出masterSecret","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这里需要提一嘴的是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.ecdheParams","attrs":{}}],"attrs":{}},{"type":"text","text":",该值为","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Client发送HelloMsg","attrs":{}},{"type":"text","text":"这一小节调用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"generateECDHEParameters","attrs":{}}],"attrs":{}},{"type":"text","text":"函数生成的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"params","attrs":{}}],"attrs":{}},{"type":"text","text":"。其他逻辑和Server生成握手阶段的密钥保持一致,硬要说不同的话也就只有","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"masterSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"生成的阶段不同。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"clientSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"在客户端用于对要发送的数据进行加密,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"在客户端对收到的数据进行解密。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"小结","attrs":{}},{"type":"text","text":":本节梳理了客户端处理","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"的逻辑和生成握手阶段密钥的逻辑。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"处理Server发送的参数","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在客户端需要处理的Server参数只有一个","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"encryptedExtensionsMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"消息。而且处理逻辑也十分简单:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"msg, err := c.readHandshake()\nencryptedExtensions, ok := msg.(*encryptedExtensionsMsg)\nhs.transcript.Write(encryptedExtensions.marshal())\nc.clientProtocol = encryptedExtensions.alpnProtocol","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果客户端读取到","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"encryptedExtensionsMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"消息,则直接将Server支持的HTTP协议赋值给","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"c.clientProtocol","attrs":{}}],"attrs":{}},{"type":"text","text":"。在之后的HTTP请求中会根据TLS握手状态以及服务端是否支持","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"h2","attrs":{}}],"attrs":{}},{"type":"text","text":"决定是否将本次请求升级为","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"http2","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"验证证书和签名","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本小节仍然继续处理Server发送的消息,主要包含","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateRequestMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateVerifyMsg","attrs":{}}],"attrs":{}},{"type":"text","text":",这三个消息均和证书相关。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,处理","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateRequestMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"消息,仅在双向认证时,服务端才发送此消息。在本阶段的处理逻辑也很简单,读取该消息并记录。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"msg, err := c.readHandshake()\ncertReq, ok := msg.(*certificateRequestMsgTLS13)\nif ok {\n hs.transcript.Write(certReq.marshal())\n hs.certReq = certReq\n msg, err = c.readHandshake()\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其次,处理","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"消息,该消息中主要包含证书信息,Client在获取到证书信息后要校验证书是否过期以及是否可信任。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"if err := c.verifyServerCertificate(certMsg.certificate.Certificate); err != nil {\n return err\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"c.verifyServerCertificate","attrs":{}}],"attrs":{}},{"type":"text","text":"的内部逻辑如果各位读者有兴趣可以下载Demo调试一番,笔者在这里就不对该方法做深入的展开和分析了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后,处理","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateVerifyMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"消息。前面在处理","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"时已经验证了证书可信任或者Client可以忽略不受信任的证书,但是Client仍无法确信提供这个证书的服务器是否持有该证书,而验证签名的意义就在于确保该服务确实持有该证书。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在Server发送","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateVerifyMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"消息时已经使用了证书对应的私钥对需要签名的数据进行签名,客户端利用证书的公钥解密该签名并和本地的待签名数据做对比以确保服务端确实持有该证书。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"// 根据签名算法返回对应的算法类型和hash算法\nsigType, sigHash, err := typeAndHashFromSignatureScheme(certVerify.signatureAlgorithm)\nsigned := signedMessage(sigHash, serverSignatureContext, hs.transcript)\nif err := verifyHandshakeSignature(sigType, c.peerCertificates[0].PublicKey,\n\tsigHash, signed, certVerify.signature); err != nil {\n c.sendAlert(alertDecryptError)\n return errors.New(\"tls: invalid signature by the server certificate: \" + err.Error())\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"typeAndHashFromSignatureScheme","attrs":{}}],"attrs":{}},{"type":"text","text":"函数和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"signedMessage","attrs":{}}],"attrs":{}},{"type":"text","text":"函数在前文已经提到过,因此不再做重复叙述。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"verifyHandshakeSignature","attrs":{}}],"attrs":{}},{"type":"text","text":"函数的内部实现涉及到非对称加密算法的加解密,因笔者的知识有限,确实无法做更进一步的分析,在这里给各位读者道个歉~","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"小结","attrs":{}},{"type":"text","text":":在这一小节简单介绍了客户端证书的验证以及签名的验证。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"处理finishedMsg并再次计算密钥","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客户端对证书签名验证通过后,接下来还需要验证消息的完整性。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"finished, ok := msg.(*finishedMsg)\nexpectedMAC := hs.suite.finishedHash(c.in.trafficSecret, hs.transcript)\nif !hmac.Equal(expectedMAC, finished.verifyData) {\n c.sendAlert(alertDecryptError)\n return errors.New(\"tls: invalid server finished hash\")\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"finishedHash","attrs":{}}],"attrs":{}},{"type":"text","text":"方法说明请参考","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"发送finishedMsg并再次计算密钥","attrs":{}},{"type":"text","text":"这一小节。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"只有当客户端计算的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"expectedMAC","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedMsg.verifyData","attrs":{}}],"attrs":{}},{"type":"text","text":"一致时才可继续后续操作,即客户端二次计算密钥。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"hs.trafficSecret = hs.suite.deriveSecret(hs.masterSecret,\n\tclientApplicationTrafficLabel, hs.transcript)\nserverSecret := hs.suite.deriveSecret(hs.masterSecret,\n\tserverApplicationTrafficLabel, hs.transcript)\nc.in.setTrafficSecret(hs.suite, serverSecret)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"二次计算密钥时分别派生出","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"trafficSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"两个密钥。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需要注意的是,此时利用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"生成的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AEAD","attrs":{}}],"attrs":{}},{"type":"text","text":"加密算法会用于握手结束后对收到的业务数据进行解密。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至此,Server发送给客户端的消息已经全部处理完毕。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"小结","attrs":{}},{"type":"text","text":":本节主要介绍了客户端通过","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"HMAC","attrs":{}}],"attrs":{}},{"type":"text","text":"算法确保收到的消息未被篡改以及二次计算密钥。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Client发送最后的消息","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客户端已经验证了服务端消息的完整性,但是服务端还未验证客户端消息的完整性,因此客户端还需要发送最后一次数据给服务端。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先判断是否需要发送证书给Server:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"if hs.certReq == nil {\n return nil\n}\ncertMsg := new(certificateMsgTLS13)\n// 此处省略代码\ncertVerifyMsg := new(certificateVerifyMsg)\ncertVerifyMsg.hasSignatureAlgorithm = true\n// 此处省略代码","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根据","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"验证证书和签名","attrs":{}},{"type":"text","text":"这一小节的描述,如果服务端要求客户端发送证书则","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.certReq","attrs":{}}],"attrs":{}},{"type":"text","text":"不为nil。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"certificateMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"的主体也是证书,该证书的来源为客户端","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"tls.Config","attrs":{}}],"attrs":{}},{"type":"text","text":"配置的证书,在本例中客户端配置证书逻辑如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"tlsConf.NextProtos = append(tlsConf.NextProtos, \"h2\", \"http/1.1\")\ntlsConf.Certificates = make([]tls.Certificate, 1)\nif len(certFile) > 0 && len(keyFile) > 0 {\n var err error\n tlsConf.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)\n if err != nil {\n return nil, err\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"既然要发送证书给服务端,那么同服务端逻辑一样也需要发送","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateVerifyMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"提供消息签名的信息。客户端签名逻辑和服务端签名逻辑一致,因此笔者不再赘述。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后,客户端需要发送","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"给服务端:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"finished := &finishedMsg{\n verifyData: hs.suite.finishedHash(c.out.trafficSecret, hs.transcript),\n}\nhs.transcript.Write(finished.marshal())\nc.out.setTrafficSecret(hs.suite, hs.trafficSecret)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需要注意的是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.trafficSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"在第二次计算密钥时就已经被赋值,当","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"发送后,利用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.trafficSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"生成的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AEAD","attrs":{}}],"attrs":{}},{"type":"text","text":"加密算法会对客户端要发送的业务数据进行加密。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至此,客户端的握手流程全部完成。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"小结","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、如果服务端要求客户端发送证书,则客户端会发送","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateVerifyMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"消息","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、发送","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"消息并设置发送业务数据时的密钥信息。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Server读Client最后的消息","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,服务端在TLS握手的最后阶段,会先判断是否要求客户端发送证书,如果要求客户端发送证书则处理客户端发送的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateVerifyMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"消息。服务端处理","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateVerifyMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"消息的逻辑和客户端处理这两个消息的逻辑类似。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其次,读取客户端发送的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedMsg","attrs":{}}],"attrs":{}},{"type":"text","text":", 并验证消息的完整性,验证逻辑和客户端验证","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"逻辑一致。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后,设置服务端读取业务数据时的加密信息:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"c.in.setTrafficSecret(hs.suite, hs.trafficSecret)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"hs.trafficSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"在服务端第二次计算加密信息时就已经赋值,当读完客户端发送的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"之后再执行此步骤是为了避免无法解密客户端发送的握手信息。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至此,服务端的握手流程全部完成。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"握手完成之后","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"完成上述流程后,笔者还想试试看能不能从握手过程获取的密钥信息对业务数据进行解密。说干就干,下面是笔者在TLS握手完成之后用Client连接发送了一条消息的代码。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"// main.go 握手完成之后,client发送了一条数据\nclient.Write([]byte(\"点赞关注:新世界杂货铺\"))","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面是运行Demo后的输出截图:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/88/883a1a9dd9a6cad0ecf7517924963124.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"图中红色箭头部分为在Internet中真实传输的数据,蓝色箭头部分为其解密结果。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"一点感慨","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"关于TLS握手流程的文章笔者想写很久了,现在总算得偿所愿。笔者不敢保证把TLS握手过程的每一个细节都描述清楚,所以如果中间有什么问题还请各位读者及时指出,大家相互学习。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"写到这里时笔者的内心也略有忐忑,毕竟这中间涉及了很多密码学相关的知识,而在笔者各种疯狂查资料期间发现国内具有权威性的文章还是太少。像","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ECDH","attrs":{}}],"attrs":{}},{"type":"text","text":"之类的关键词在百度百科都没有收录,果然维基百科才是爸爸呀。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后一点感概是关于Go中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"io.Reader ","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"io.Writer","attrs":{}}],"attrs":{}},{"type":"text","text":"这两个接口的,不得不说这两个接口的设计真的很简单但是真的非常通用。笔者的Demo正是基于这两个接口实现,否则笔者的心愿很难完成。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"挖坑","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在上一篇文章中,笔者给了一条彩蛋——“下一期TLS/SSL握手流程敬请期待”。哇,这可真的是自己坑自己了,本篇文章未完成之前,笔者愣是断更了也没敢发别的文章。果然自己作的死,哭着也要作完。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有了前车之鉴,笔者决定以后不再放彩蛋,而是挖坑(填坑时间待定😊):本篇中主要介绍了TLS1.3的握手流程,那么TLS1.2也快了~","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后,衷心希望本文能够对各位读者有一定的帮助。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"注","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1. 写本文时, 笔者所用go版本为: go1.15.2","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2. 文章中所用完整例子:https://github.com/Isites/go-coder/blob/master/http2/tls/main.go","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章