前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go语言TLS客户端握手

Go语言TLS客户端握手

作者头像
用户10714163
发布2023-08-18 14:42:12
5520
发布2023-08-18 14:42:12
举报
文章被收录于专栏:计算机知识计算机知识

客户端在完成 clientHelloMsg 和 serverHelloMsg 后,开启缓存写入模式,也分为重用 session 和非重用 session 两种情况。服务端在发送完第一批次消息后,等待客户端的回应。

客户端

判断如果复用的话. 处理流程为1. 生成相关的Key 2. 读Session Ticket 3. 读Finished 报文 4. 发送缓冲区内容

如果没有复用的话. 处理流程则需要现做完整的握手

代码语言:javascript
复制
// 判断是否复用
	if isResume {
		if err := hs.establishKeys(); err != nil { return err }
		if err := hs.readSessionTicket(); err != nil { return err }
		if err := hs.readFinished(c.serverFinished[:]); err != nil { return err }
		c.clientFinishedIsFirst = false
		if err := hs.sendFinished(c.clientFinished[:]); err != nil { return err }
		if _, err := c.flush(); err != nil { return err }
	} else {
		if err := hs.doFullHandshake(); err != nil { return err }
		if err := hs.establishKeys(); err != nil { return err }
		if err := hs.sendFinished(c.clientFinished[:]); err != nil { return err }
		if _, err := c.flush(); err != nil { return err }
		c.clientFinishedIsFirst = true
		if err := hs.readSessionTicket(); err != nil { return err }
		if err := hs.readFinished(c.serverFinished[:]); err != nil { return err }
	}

1 非重用 session

  完成 serverHelloMsg 的处理后,在 fullhandshake 模式下,需要对服务端依次发送的 certificateMsg,certificateStatusMsg(可选),serverKeyExchangeMsg(非 RSA 秘钥交换),certificateRequestMsg(可选),serverHelloDoneMsg 进行处理。代码在./tls/handshake_client.go中的doFullHandshake()中。,在Client 做DH密钥交换之前,需先验证服务端证书。主要的函数为在./tls/handshake_client.go中的

verifyServerCertificate()

1.1 doFullHandshake

  1. 读取服务端证书信息certificateMsg,并完成
  2. 如果是首次握手,则解析证书,验证证书,保存有效证书到c.peerCertificates;如果不是首次握手,检查消息中的证书与之前存储在c.peerCertificates的证书是否相同
  3. 按需读取certificateStatusMsg(可选),并完成,存储证书状态到c.ocspResponse
  4. 读取serverKeyExchangeMsg(非 RSA 秘钥交换),完成消息,调用 processServerKeyExchange,从中获取服务端秘钥交换的公钥点,椭圆曲线,以及服务端用其证书私钥对椭圆曲线公钥信息的签名。客户端用服务端证书公钥对此签名进行认证。
  5. 读取 certificateRequestMsg(可选),按需获取客户端证书,完成该消息。
  6. 读取并完成 serverHelloDoneMsg
  7. 如果需要发送客户端证书信息,构造并完成 certificateMsg ,写入 sendBuf,等待推送
  8. 调用 generateClientKeyExchange,   如果是 RSA 秘钥交换,生成46字节随机数,加上头两个字节的TLS版本值,就是预备主密钥,用服务端证书公钥对此预备主密钥加密,构造clientKeyExchangeMsg。   如果是 ECDHE 秘钥交换,随机生成客户端秘钥交换私钥,和客户端秘钥交换公钥,构造clientKeyExchangeMsg,生成客户端秘钥交换公钥信息。再根据之前获得的服务端秘钥交换公钥,生成客户端预备主密钥。   完成clientKeyExchangeMsg,写入 sendBuf,等待推送
  9. 如果之前发送了客户端的证书,也要发送签名消息 certificateVerifyMsg,签名的对象是 finishedHash 中所有的握手消息(finishedHash.buffer)的 hash 值。完成该消息,写入 sendBuf,等待推送。从这里也就说明了为什么 buffer 只有在需要客户端证书的情况下才不为 nil。
  10. 根据预备主密钥,客户端随机数,服务端随机数,计算主密钥
  11. 清空 finishedHash 中的 buffer

  目前缓存待推送的消息:certificateMsg(可选),clientKeyExchangeMsg,certificateVerifyMsg(可选)

代码语言:javascript
复制
//客户端 fullhandshake
func (hs *clientHandshakeState) doFullHandshake() error {
	c := hs.c

	// ServerHelloMsg之后,期待收到服务端的证书信息,证书必须存在且不为空
	msg, err := c.readHandshake()
	if err != nil {
		return err
	}
	certMsg, ok := msg.(*certificateMsg)
	if !ok || len(certMsg.certificates) == 0 {
		c.sendAlert(alertUnexpectedMessage)
		return unexpectedMessageError(certMsg, msg)
	}
	// fihishedHash 中完成certificateMsg
	hs.finishedHash.Write(certMsg.marshal())

	if c.handshakes == 0 {
		// 如果是初次握手,解析证书
		// If this is the first handshake on a connection, process and
		// (optionally) verify the server's certificates.
		certs := make([]*x509.Certificate, len(certMsg.certificates))
		for i, asn1Data := range certMsg.certificates {
			cert, err := x509.ParseCertificate(asn1Data)
			if err != nil {
				c.sendAlert(alertBadCertificate)
				return errors.New("tls: failed to parse certificate from server: " + err.Error())
			}
			certs[i] = cert
		}

		//如果不是跳过验证,对证书进行验证,并更新 c.verifiedChains,c.peerCertificates
		if !c.config.InsecureSkipVerify {
			opts := x509.VerifyOptions{
				Roots:         c.config.RootCAs,
				CurrentTime:   c.config.time(),
				DNSName:       c.config.ServerName,
				Intermediates: x509.NewCertPool(),
			}

			for i, cert := range certs {
				if i == 0 {
					continue
				}
				opts.Intermediates.AddCert(cert)
			}
			c.verifiedChains, err = certs[0].Verify(opts)
			if err != nil {
				c.sendAlert(alertBadCertificate)
				return err
			}
		}

		//自定义的验证
		if c.config.VerifyPeerCertificate != nil {
			if err := c.config.VerifyPeerCertificate(certMsg.certificates, c.verifiedChains); err != nil {
				c.sendAlert(alertBadCertificate)
				return err
			}
		}

		// 只支持 RSA,ECDSA 的公钥
		switch certs[0].PublicKey.(type) {
		case *rsa.PublicKey, *ecdsa.PublicKey:
			break
		default:
			c.sendAlert(alertUnsupportedCertificate)
			return fmt.Errorf("tls: server's certificate contains an unsupported type of public key: %T", certs[0].PublicKey)
		}

		//保存解析后的证书
		c.peerCertificates = certs
	} else {
		//不是首次握手,需确保之前存储的服务端的证书没有改变
		// This is a renegotiation handshake. We require that the
		// server's identity (i.e. leaf certificate) is unchanged and
		// thus any previous trust decision is still valid.
		//
		// See https://mitls.org/pages/attacks/3SHAKE for the
		// motivation behind this requirement.
		if !bytes.Equal(c.peerCertificates[0].Raw, certMsg.certificates[0]) {
			c.sendAlert(alertBadCertificate)
			return errors.New("tls: server's identity changed during renegotiation")
		}
	}

	//再次读取 handshake 的 msg,此时有可能是客户端主动要求服务端发送的 certificateStatusMsg
	msg, err = c.readHandshake()
	if err != nil {
		return err
	}

	cs, ok := msg.(*certificateStatusMsg)
	if ok {
		// RFC4366 on Certificate Status Request:
		// The server MAY return a "certificate_status" message.

		//如果服务端ocspStapling标记为假,但收到了certificateStatusMsg,报警
		if !hs.serverHello.ocspStapling {
			// If a server returns a "CertificateStatus" message, then the
			// server MUST have included an extension of type "status_request"
			// with empty "extension_data" in the extended server hello.

			c.sendAlert(alertUnexpectedMessage)
			return errors.New("tls: received unexpected CertificateStatus message")
		}
		// 累计计算 hash,并置 c.ocspResponse = cs.response
		hs.finishedHash.Write(cs.marshal())

		if cs.statusType == statusTypeOCSP {
			c.ocspResponse = cs.response
		}

		//  读取下一条 handshake 消息
		msg, err = c.readHandshake()
		if err != nil {
			return err
		}
	}

	// 根据套件获取对应的 keyAgreement
	keyAgreement := hs.suite.ka(c.vers)

	// 可能是 serverKeyExchangeMsg,如果采用的是 RSA 交换秘钥的模式,没有 serverKeyExchangeMsg
	skx, ok := msg.(*serverKeyExchangeMsg)
	if ok {
		// 如果是 serverKeyExchangeMsg,完成该消息并处理
		hs.finishedHash.Write(skx.marshal())
		//  从 serverKeyExchangeMsg 获取服务端公钥,并验证签名
		err = keyAgreement.processServerKeyExchange(c.config, hs.hello, hs.serverHello, c.peerCertificates[0], skx)
		if err != nil {
			c.sendAlert(alertUnexpectedMessage)
			return err
		}

		// 读取下一条消息
		msg, err = c.readHandshake()
		if err != nil {
			return err
		}
	}

	var chainToSend *Certificate
	var certRequested bool
	certReq, ok := msg.(*certificateRequestMsg)
	//此时可能是服务端要求客户端发送证书的请求消息
	if ok {
		certRequested = true
		// 完成该消息
		hs.finishedHash.Write(certReq.marshal())

		// 根据要求获取客户端的证书
		if chainToSend, err = hs.getCertificate(certReq); err != nil {
			c.sendAlert(alertInternalError)
			return err
		}

		// 读取下一条消息
		msg, err = c.readHandshake()
		if err != nil {
			return err
		}
	}

	// 应该是 HelloDone 消息
	shd, ok := msg.(*serverHelloDoneMsg)
	if !ok {
		c.sendAlert(alertUnexpectedMessage)
		return unexpectedMessageError(shd, msg)
	}
	//] 完成消息
	hs.finishedHash.Write(shd.marshal())

	// If the server requested a certificate then we have to send a
	// Certificate message, even if it's empty because we don't have a
	// certificate to send.
	// 发送客户端证书信息
	if certRequested {
		certMsg = new(certificateMsg)
		certMsg.certificates = chainToSend.Certificate
		hs.finishedHash.Write(certMsg.marshal())
		if _, err := c.writeRecord(recordTypeHandshake, certMsg.marshal()); err != nil {
			return err
		}
	}

	// 构造客户端秘钥交换消息,由于已经收到服务端的公钥信息,所以同时可以生成预备主密钥了
	preMasterSecret, ckx, err := keyAgreement.generateClientKeyExchange(c.config, hs.hello, c.peerCertificates[0])
	if err != nil {
		c.sendAlert(alertInternalError)
		return err
	}
	// 完成消息,并将 clientKeyExchangeMsg 写入 sendBuf,等待推送
	if ckx != nil {
		hs.finishedHash.Write(ckx.marshal())
		if _, err := c.writeRecord(recordTypeHandshake, ckx.marshal()); err != nil {
			return err
		}
	}

	// 如果发送了证书给服务端,那么也要发送签名让服务端验证
	if chainToSend != nil && len(chainToSend.Certificate) > 0 {
		certVerify := &certificateVerifyMsg{
			hasSignatureAndHash: c.vers >= VersionTLS12,
		}

		//证书支持签名
		key, ok := chainToSend.PrivateKey.(crypto.Signer)
		if !ok {
			c.sendAlert(alertInternalError)
			return fmt.Errorf("tls: client certificate private key of type %T does not implement crypto.Signer", chainToSend.PrivateKey)
		}

		// 证书的签名类型,RSA 或 ECDSA
		var signatureType uint8
		switch key.Public().(type) {
		case *ecdsa.PublicKey:
			signatureType = signatureECDSA
		case *rsa.PublicKey:
			signatureType = signatureRSA
		default:
			c.sendAlert(alertInternalError)
			return fmt.Errorf("tls: failed to sign handshake with client certificate: unknown client certificate key type: %T", key)
		}

		// SignatureAndHashAlgorithm was introduced in TLS 1.2.
		if certVerify.hasSignatureAndHash {
			// 以证书的签名类型为基准,获取一种符合服务端要求的签名算法
			certVerify.signatureAlgorithm, err = hs.finishedHash.selectClientCertSignatureAlgorithm(certReq.supportedSignatureAlgorithms, signatureType)
			if err != nil {
				c.sendAlert(alertInternalError)
				return err
			}
		}
		// 计算finishedHash.buffer 的 hash值,作为待签名对象
		digest, hashFunc, err := hs.finishedHash.hashForClientCertificate(signatureType, certVerify.signatureAlgorithm, hs.masterSecret)
		if err != nil {
			c.sendAlert(alertInternalError)
			return err
		}
		// 对上述 hash 值以私钥采用同样的 hash 函数进行签名
		certVerify.signature, err = key.Sign(c.config.rand(), digest, hashFunc)
		if err != nil {
			c.sendAlert(alertInternalError)
			return err
		}

		// 累计计算 hash,将 certificateVerifyMsg 写入 conn 缓存
		hs.finishedHash.Write(certVerify.marshal())
		if _, err := c.writeRecord(recordTypeHandshake, certVerify.marshal()); err != nil {
			return err
		}
	}

	// 根据预备主密钥,客户端随机数,服务端随机数,计算主密钥
	hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.hello.random, hs.serverHello.random)
	// 一般测试用
	if err := c.config.writeKeyLog(hs.hello.random, hs.masterSecret); err != nil {
		c.sendAlert(alertInternalError)
		return errors.New("tls: failed to write to key log: " + err.Error())
	}

	//  清空 finishedHash 中的 buffer
	hs.finishedHash.discardHandshakeBuffer()

	return nil
}

2 RSA 握手

1.浏览器向服务器发送随机数 client_random,TLS 版本和供筛选的加密套件列表。

2.服务器接收到,立即返回 server_random,确认好双方都支持的加密套件 以及数字证书 (证书中附带公钥 Public key certificate)。

3.浏览器接收,先验证数字证书。若通过,接着使用加密套件的密钥协商算法 RSA 算法生成另一个随机数 pre_random,并且用证书里的公钥加密,传给服务器。

4.服务器用私钥解密这个被加密后的 pre_random,参考 “非对称加密”。

现在浏览器和服务器都拥有三样相同的凭证:client_random、server_random 和 pre_random。两者都用筛好的加密套件中的加密方法混合这三个随机数,生成最终的密钥。

最后,浏览器和服务器使用相同的密钥进行通信,即使用 对称加密

到这里,还有两点需要注意。

第一:握手中的任何消息均未使用秘钥加密,它们都是明文发送的。

第二:TLS 握手其实是一个 双向认证 的过程,客户端和服务器都需要进行摘要认证以及收尾 (发送 Finished 消息,意为后面 HTTP 开始正式加密报文通信了)。

3 DH 握手

1.浏览器向服务器发送随机数 client_random,TLS 版本和供筛选的加密套件列表。

// RSA

-2.服务器接收到,立即返回 server_random,确认好双方都支持的加密套件 -以及数字证书 (证书中附带公钥)。

// DH

+2.服务器接收到,立即返回 server_random,确认好双方都支持的加密套件 +以及数字证书 (证书中附带公钥)。 +同时服务器利用私钥将 client_random,server_random,server_params 签名, +生成服务器签名。然后将签名和 server_params 也发送给客户端。 +这里的 server_params 为 DH 算法所需参数。

// RSA

-3.浏览器接收,先验证数字证书。 -若通过,接着使用加密套件的密钥协商算法 RSA 算法 -生成另一个随机数 pre_random,并且用证书里的公钥加密,传给服务器。

// DH

+3.浏览器接收,先验证数字证书和 _签名_。 +若通过,将 client_params 传递给服务器。 +这里的 client_params 为 DH 算法所需参数。

-4.服务器用私钥解密这个被加密后的 pre_random,参考 “非对称加密”。

+4.现在客户端和服务器都有 client_params、server_params 两个参数, +因 ECDHE 计算基于 “椭圆曲线离散对数”,通过这两个 DH 参数就能计算出 pre_random。

现在浏览器和服务器都拥有三样相同的凭证:client_random、server_random 和 pre_random,后续步骤与 RSA 握手一致。

DH 握手前向安全性

TLS1.2 握手

有了前面一节的概念后,TLS1.2 握手理解起来就显得毫不费力了。因为主流的 TLS1.2 握手就是上节完整的 DH 握手流程。

TLS1.3 握手

互联网的世界飞速发展,随着时间的推移,人们早已不满足于现有的 TLS1.2。于是,更快、更安全的 “船新版本” TLS1.3 发布了。

TLS1.3 废除了原有的部分不安全的加密算法,其中甚至包括 RSA 算法。

RSA 算法的废除不仅因为已经有大能将其破解,同时还缺少 前向安全性。何为前向安全?即能够保护过去进行的通讯不受密码或密钥在未来暴露的威胁。

现在我们来看看 TLS1.3 握手具体流程,先放图:

// 原 DH 握手

-1.浏览器向服务器发送 client_random,TLS 版本和供筛选的加密套件列表。

// TLS1.3 优化

+1.浏览器向服务器发送 client_params,client_random,TLS 版本和供筛选的加密套件列表。

// 原 DH 握手

-2...

// TLS1.3 优化

+2.服务器返回:server_random、server_params、TLS 版本、确定的加密套件方法以及证书。 +浏览器接收,先验证数字证书和签名。

最后,集齐三个参数,生成最终秘钥。

如你所见,TLS1.3 客户端和服务器之间只需要一次往返就完成 (TLS1.2 需要两次往返来完成握手),即 1-RTT 握手。当然,如果利用 PSK 我们甚至能优化到 0-RTT (这并不好,安全受到质疑~)。

小结

以上,我们梳理了三个版本的 TLS 握手流程,它们其实属于两类:一种基于 RSA,另一种基于 Diffie-Hellman (ECDHE)。这两类握手的区别仅在于如何实现秘钥建立和身份验证:

秘钥交换

身份验证

RSA 握手

RSA

RSA

DH 握手

ECDHE

RSA/DSA

我们看到,在版本演进中,最新的 TLS1.3 抛弃了 RSA 算法是为了更安全,减少 RTT 是为了更快。可知,互联网技术革新的特点不外如是。

参考链接:https://juejin.cn/post/6895624327896432654

https://kiosk007.top/post/tls%E8%AF%A6%E8%A7%A3-%E4%BA%8C/#client-key-exchange

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 客户端
    • 1 非重用 session
      • 1.1 doFullHandshake
    • 2 RSA 握手
      • 3 DH 握手
        • TLS1.2 握手
          • TLS1.3 握手
            • 小结
            相关产品与服务
            多因子身份认证
            多因子身份认证(Multi-factor Authentication Service,MFAS)的目的是建立一个多层次的防御体系,通过结合两种或三种认证因子(基于记忆的/基于持有物的/基于生物特征的认证因子)验证访问者的身份,使系统或资源更加安全。攻击者即使破解单一因子(如口令、人脸),应用的安全依然可以得到保障。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档