Go语言TLS客户端握手

2023-08-18 14:42:12 浏览数 (1)

客户端在完成 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详解-二/#client-key-exchange

0 人点赞