以 go 语言demo为例进行断点验证

写在前面

在 《自签名证书中的 CA 证书以及 TLS 证书》中我们知道,ca 根证书一般在装机的时候便已经被安装,因此系统可以发起签名认证。但是喜欢观察的同学应该会注意到,我们在 https 协议中使用的公钥证书都不是由根证书直接签名,而是经过中间证书的签名。那么这又是一种什么样的机制呢?

正文

中间证书涉及到的几个概念

如上图所示,我们可以把 ca-key.pemca.pem 密钥称为根证书,而 ca-key2.pemca2.pem 密钥称为中间证书。按照上图的示例,根证书签名得到中间证书,而中间证书签名得到业务证书(在实践中,中间证书可以存在多层,以此串联起来形成证书链)。

接下来我们以一个具体的例子 https://baidu.com 为例简单分析一下。

以 baidu.com 为例

百度网页证书示例

上图是 baidu.com 证书的几个信息,通过 safari 浏览器截取。通过上面的图我们可以看出来,baidu.com 证书是由 GlobalSign RSA OV SSL CA 2018 中间证书进行签发,而后者又是由 GlobalSign 进行签发。

其中 GlobalSign(GlobalSign Root CA - R3) 可以在我本地的钥匙串(mac系统管理密钥的工具)中找到,那么中间证书是如何拿到的呢?

通过 《ngx_http_ssl_module - ssl_certificate》 我们可以知道,在 nginx 的配置中,中间证书会要求和业务证书放置在一起。这样方便浏览器或者其他的客户端在获取业务公钥的同时获取到中间证书的公钥,从而便利地验证证书链。

源码参考

为了方便确认,可以基于 go 语言的一小段源码进行断点调试,确认 tls 连接建立过程中公钥的获取与分析。

demo源码

package main

import (
	"fmt"
	"net/http"
)

func main() {
	rsp, err := http.Get("https://baidu.com")
	if err != nil {
		fmt.Printf("get error err=%s", err.Error())
		return
	}

	contents := [1024]byte{}
	len, err := rsp.Body.Read(contents[:])
	if err != nil {
		fmt.Printf("Read error err=%s", err.Error())
		return
	}
	fmt.Printf("body=%s", contents[:len])
}

涉及到的几个断点


// src/net/http/transport.go
// 建立连接
func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (pconn *persistConn, err error) {
}


// src/net/http/transport.go
// 添加证书
func (pconn *persistConn) addTLS(ctx context.Context, name string, trace *httptrace.ClientTrace) error {
}

// src/crypto/tls/handshake_client.go
// tls 协议中的握手流程
func (c *Conn) clientHandshake(ctx context.Context) (err error) {
}

// src/crypto/tls/handshake_client.go
// tls 协议总的握手全部流程,其中就会尝试从 tpc 连接中获取公钥证书
func (hs *clientHandshakeState) doFullHandshake() error {
}

// src/crypto/tls/handshake_client.go
// 对公钥进行验证
func (c *Conn) verifyServerCertificate(certificates [][]byte) error {
}

小结

本文简单介绍了 ssl 证书链的几个概念以及运行机制。最后提供了一个 go 语言的例子及断点方法,方便读者自行 debug。

参考