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

本文详细解析了TLS1.3握手流程,包括单向认证和双向认证,通过2000多行代码实现的调试Demo。文章介绍了客户端和服务器的交互,如HelloMsg、证书、签名、密钥交换和消息验证等,展示了TLS1.3握手过程中数据的收发和加密处理。同时,提供了源码链接供读者深入研究。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

前言

呼,这篇文章的准备周期可谓是相当的长了!原本是想直接通过源码进行分析的,但是发现TLS握手流程调试起来非常不方便,笔者怒了,于是实现了一个极简的net.Conn接口以方便调试。码着码着,笔者哭了,因为现在这个调试Demo已经达到2000多行代码了!

在这里插入图片描述

虽然码了两千多行代码,但是目前只能够解析TLS1.3握手流程中发送的消息,因此本篇主要分析TLS1.3的握手流程。

特别提醒:有想在本地调试一番的小伙伴请至文末获取本篇源码。

结论先行

鉴于本文篇幅较长,笔者决定结论先行,以助各位读者理解后文详细的分析内容。

HTTPS单向认证

单向认证客户端不需要证书,客户端只要验证服务端证书合法即可访问。

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

在这里插入图片描述

根据调试信息知,在TLS1.3单向认证中,总共收发数据三次,Client和Server从这三次数据中分别读取不同的信息以达到握手的目的。

注意:TLS1.3不处理ChangeCipherSpec类型的数据,而该数据在TLS1.2中是需要处理的。因本篇主要分析TLS1.3握手流程,故后续不会再提及ChangeCipherSpec,同时时序图中也会忽略此消息

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

在这里插入图片描述

HTTPS双向认证

双向认证不仅服务端要有证书,客户端也需要证书,只有客户端和服务端证书均合法才可继续访问。

笔者在这里特别提醒,开启双向认证很简单,在笔者的Demo中取消下面代码的注释即可。

// sconf.ClientAuth = tls.RequireAndVerifyClientCert

另外,笔者在main.go同目录下留有测试用的根证书、服务端证书和客户端证书,为了保证双向认证的顺利运行请将根证书安装为受用户信任的证书。

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

在这里插入图片描述

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

在这里插入图片描述

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

双向认证中,Client和Server发送消息变多了,但是总的数据收发仍然只有三次

总结

1、TLS1.3和TLS1.2握手流程是有区别的,这一点需要注意。

2、单向认证和双向认证中,总的数据收发仅三次,单次发送的数据中包含一个或者多个消息。

3、clientHelloMsgserverHelloMsg未经过加密,之后发送的消息均做了加密处理。

4、Client和Server会各自计算两次密钥,计算时机分别是读取到对方的HelloMsgfinishedMsg之后。

:上述第3点和第4点分析过程详见后文。

Client发送HelloMsg

在TLS握手过程中的第一步是Client发送HelloMsg,所以针对TLS握手流程的分析也从这一步开始。

Server对于Client的基本信息了解完全依赖于Client主动告知Server,而其中比较关键的信息分别是客户端支持的TLS版本客户端支持的加密套件(cipherSuites)客户端支持的签名算法客户端支持的密钥交换协议以及其对应的公钥

客户端支持的TLS版本:

客户端支持的TLS版本主要通过tls包中(*Config).supportedVersions方法计算。对TLS1.3来说默认支持的TLS版本如下:

var supportedVersions = []uint16{
   
	VersionTLS13,
	VersionTLS12,
	VersionTLS11,
	VersionTLS10,
}

在发起请求时如果用户手动设置了tls.Config中的MaxVersion或者MinVersion,则客户端支持的TLS版本会发生变化。

例如发起请求时,设置了conf.MaxVersion = tls.VersionTLS12,此时(*Config).supportedVersions返回的版本为:

[]uint16{
   
	VersionTLS12,
	VersionTLS11,
	VersionTLS10,
}

ps: 如果有兴趣的小伙伴可以在克隆笔者的demo后手动设置Config.MaxVersion,设置后可以调试TLS1.2的握手流程。

客户端支持的加密套件(cipherSuites):

说实话,加密套件已经进入笔者的知识盲区了,其作用笔者会在下一小节讲明白,故本小节笔者直接贴出计算后的结果。

在这里插入图片描述

图中篮框部分为当前Client支持加密套件Id,红框部分为计算逻辑。

客户端支持的签名算法:

客户端支持的签名算法,仅在客户端支持的最大TLS版本大于等于TLS1.2时生效。此时客户端支持的签名算法如下:

var supportedSignatureAlgorithms = []SignatureScheme{
   
	PSSWithSHA256,
	ECDSAWithP256AndSHA256,
	Ed25519,
	PSSWithSHA384,
	PSSWithSHA512,
	PKCS1WithSHA256,
	PKCS1WithSHA384,
	PKCS1WithSHA512,
	ECDSAWithP384AndSHA384,
	ECDSAWithP521AndSHA512,
	PKCS1WithSHA1,
	ECDSAWithSHA1,
}

客户端支持的密钥交换协议及其对应的公钥:

这一块儿逻辑仅在客户端支持的最大TLS版本是TLS1.3时生效。

if hello.supportedVersions[0] == VersionTLS13 {
   
	hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13()...)

	curveID := config.curvePreferences()[0]
	if _, ok := curveForCurveID(curveID); curveID != X25519 && !ok {
   
		return nil, nil, errors.New("tls: CurvePreferences includes unsupported curve")
	}
	params, err = generateECDHEParameters(config.rand(), curveID)
	if err != nil {
   
		return nil, nil, err
	}
	hello.keyShares = []keyShare{
   {
   group: curveID, data: params.PublicKey()}}
}

上述代码中,方法config.curvePreferences的逻辑为:

var defaultCurvePreferences = []CurveID{
   X25519, CurveP256, CurveP384, CurveP521}
func (c *Config) curvePreferences() []CurveID {
   
	if c == nil || len(c.CurvePreferences) == 0 {
   
		return defaultCurvePreferences
	}
	return c.CurvePreferences
}

在本篇中,笔者未手动设置优先可供选择的曲线,故curveID的值为X25519

上述代码中,generateECDHEParameters函数的作用是根据曲线Id生成一种椭圆曲线密钥交换协议的实现。

如果客户端支持的最大TLS版本是TLS1.3时,会为Client支持的加密套件增加TLS1.3默认的加密套件,同时还会选择Curve25519密钥交换协议生成keyShare

小结:本节介绍了在TLS1.3中Client需要告知Server客户端支持的TLS版本号、客户端支持的加密套件、客户端支持的签名算法和客户端支持的密钥交换协议。

Server读HelloMsg&发送消息

Server读到clientHelloMsg之后会根据客户端支持的TLS版本和本地支持的TLS版本做对比,得到Client和Server均支持的TLS版本最大值,该值作为后续继续通信的标准。本篇中Client和Server都支持TLS1.3,因此Server进入TLS1.3的握手流程。

处理clientHelloMsg

Server进入TLS1.3握手流程之后,还需要继续处理clientHelloMsg,同时构建serverHelloMsg

Server支持的TLS版本:

进入TLS1.3握手流程之前,Server已经计算出两端均支持的TLS版本,但是Client还无法得知Server支持的TLS版本,因此开始继续处理clientHelloMsg时,Server将已经计算得到的TLS版本赋值给supportedVersion以告知客户端。

// client读取到serverHelloMsg后,通过读取此字段计算两端均支持的TLS版本
hs.hello.supportedVersion = c.vers

Server计算两端均支持的加密套件

clientHelloMsg中含有Client支持的加密套件信息,Server读取该信息并和本地支持的加密套件做对比计算出两端均支持的加密套件。

这里需要注意的是,如果Server的tls.Config.PreferServerCipherSuitestrue则选择Server第一个在两端均支持的加密套件,否则选择Client第一个在两端均支持的加密套件。笔者通过Debug得到两端均支持的加密套件id为4865(其常量为tls.TLS_AES_128_GCM_SHA256),详情见下图:

在这里插入图片描述

上图中的mutualCipherSuiteTLS13函数会从cipherSuitesTLS13变量中选择匹配的加密套件。

var cipherSuitesTLS13 = []*cipherSuiteTLS13{
   
	{
   TLS_AES_128_GCM_SHA256, 16, aeadAESGCMTLS13, crypto.SHA256},
	{
   TLS_CHACHA20_POLY1305_SHA256, 32, aeadChaCha20Poly1305, crypto.SHA256},
	{
   TLS_AES_256_GCM_SHA384, 32, aeadA
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值