Go TLS服务端绑定证书的几种方式

news2024/11/19 2:22:39

随着互联网的发展,网站提供的服务类型和规模不断扩大,同时也对Web服务的安全性提出了更高的要求。TLS(Transport Layer Security)[1]已然成为Web服务最重要的安全基础设施之一。默认情况下,一个TLS服务器通常只绑定一个证书[2],但当服务复杂度增加时,单一证书已然难以满足需求。这时,服务端绑定多个TLS证书就成为一个非常实用的功能。

Go语言中的net/http包和tls包对TLS提供了强大的支持,在密码学和安全专家Filippo Valsorda[3]的精心设计下,Go提供了多种TLS服务端绑定证书的方式,本文将详细探讨服务端绑定TLS证书的几种方式,包括绑定单个证书、多个证书、自定义证书绑定逻辑等。我会配合示例代码,了解每种方式的使用场景、实现原理和优缺点。

注:本文假设读者已熟悉基本的TLS使用方法[4],并具备Go语言编程经验。如果你不具备Go语言基础知识,可以将学习我撰写的极客时间专栏《Go语言第一课》[5]作为你入门Go的起点。

1. 热身:制作证书

为了后续示例说明方便,我们先来把示例所需的私钥和证书都做出来,本文涉及的证书以及他们之间的签发关系如下图:

02a26ca4d8425a89a48650176f08ea7e.png

注:示例使用的自签名根证书。

从图中我们看到,我们证书分为三个层次,最左边是CA的根证书(root certificate,比如ca-cert.pem),之后是根CA签发的中间CA证书(intermediate certificate,比如inter-cert.pem),从安全和管理角度出发,真正签发服务器证书的都是这些中间CA;最右侧则是由中间CA签发的叶子证书(leaf certificate,比如leaf-server-cert.pem),也就是服务器配置的服务端证书(server certificate),我们为三个不同域名创建了不同的服务器证书。

在这里,我们制作上述证书没有使用类似openssl[6]这样的工具,而是通过Go代码生成的,下面是生成上述证书的代码片段:

// tls-certs-binding/make_certs/main.go

func main() {
 // 生成CA根证书密钥对
 caKey, err := rsa.GenerateKey(rand.Reader, 2048)
 checkError(err)

 // 生成CA证书模板
 caTemplate := x509.Certificate{
  SerialNumber: big.NewInt(1),
  Subject: pkix.Name{
   Organization: []string{"Go CA"},
  },
  NotBefore:             time.Now(),
  NotAfter:              time.Now().Add(time.Hour * 24 * 365),
  KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
  BasicConstraintsValid: true,
  IsCA:                  true,
 }

 // 使用模板自签名生成CA证书
 caCert, err := x509.CreateCertificate(rand.Reader, &caTemplate, &caTemplate, &caKey.PublicKey, caKey)
 checkError(err)

 // 生成中间CA密钥对
 interKey, err := rsa.GenerateKey(rand.Reader, 2048)
 checkError(err)

 // 生成中间CA证书模板
 interTemplate := x509.Certificate{
  SerialNumber: big.NewInt(2),
  Subject: pkix.Name{
   Organization: []string{"Go Intermediate CA"},
  },
  NotBefore:             time.Now(),
  NotAfter:              time.Now().Add(time.Hour * 24 * 365),
  KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
  BasicConstraintsValid: true,
  IsCA:                  true,
 }

 // 用CA证书签名生成中间CA证书
 interCert, err := x509.CreateCertificate(rand.Reader, &interTemplate, &caTemplate, &interKey.PublicKey, caKey)
 checkError(err)

 // 生成叶子证书密钥对
 leafKey, err := rsa.GenerateKey(rand.Reader, 2048)
 checkError(err)

 // 生成叶子证书模板,CN为server.com
 leafTemplate := x509.Certificate{
  SerialNumber: big.NewInt(3),
  Subject: pkix.Name{
   Organization: []string{"Go Server"},
   CommonName:   "server.com",
  },
  NotBefore:    time.Now(),
  NotAfter:     time.Now().Add(time.Hour * 24 * 365),
  KeyUsage:     x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
  ExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
  IPAddresses:  []net.IP{net.ParseIP("127.0.0.1")},
  DNSNames:     []string{"server.com"},
  SubjectKeyId: []byte{1, 2, 3, 4},
 }

 // 用中间CA证书签名生成叶子证书
 leafCert, err := x509.CreateCertificate(rand.Reader, &leafTemplate, &interTemplate, &leafKey.PublicKey, interKey)
 checkError(err)

 // 生成server1.com叶子证书
 leafKey1, _ := rsa.GenerateKey(rand.Reader, 2048)

 leafTemplate1 := x509.Certificate{
  SerialNumber: big.NewInt(4),
  Subject: pkix.Name{
   CommonName: "server1.com",
  },
  NotBefore: time.Now(),
  NotAfter:  time.Now().Add(time.Hour * 24 * 365),

  KeyUsage:    x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
  ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
  DNSNames:    []string{"server1.com"},
 }

 leafCert1, _ := x509.CreateCertificate(rand.Reader, &leafTemplate1, &interTemplate, &leafKey1.PublicKey, interKey)

 // 生成server2.com叶子证书
 leafKey2, _ := rsa.GenerateKey(rand.Reader, 2048)

 leafTemplate2 := x509.Certificate{
  SerialNumber: big.NewInt(5),
  Subject: pkix.Name{
   CommonName: "server2.com",
  },
  NotBefore: time.Now(),
  NotAfter:  time.Now().Add(time.Hour * 24 * 365),

  KeyUsage:    x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
  ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
  DNSNames:    []string{"server2.com"},
 }

 leafCert2, _ := x509.CreateCertificate(rand.Reader, &leafTemplate2, &interTemplate, &leafKey2.PublicKey, interKey)

 // 将证书和密钥编码为PEM格式
 caCertPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: caCert})
 caKeyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(caKey)})

 interCertPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: interCert})
 interKeyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(interKey)})

 leafCertPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert})
 leafKeyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(leafKey)})

 leafCertPEM1 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert1})
 leafKeyPEM1 := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(leafKey1)})

 leafCertPEM2 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert2})
 leafKeyPEM2 := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(leafKey2)})

 // 将PEM写入文件
 writeDataToFile("ca-cert.pem", caCertPEM)
 writeDataToFile("ca-key.pem", caKeyPEM)

 writeDataToFile("inter-cert.pem", interCertPEM)
 writeDataToFile("inter-key.pem", interKeyPEM)

 writeDataToFile("leaf-server-cert.pem", leafCertPEM)
 writeDataToFile("leaf-server-key.pem", leafKeyPEM)

 writeDataToFile("leaf-server1-cert.pem", leafCertPEM1)
 writeDataToFile("leaf-server1-key.pem", leafKeyPEM1)

 writeDataToFile("leaf-server2-cert.pem", leafCertPEM2)
 writeDataToFile("leaf-server2-key.pem", leafKeyPEM2)
}

运行这个程序后,当前目录下就会出现如下私钥文件(xx-key.pem)和证书文件(xx-cert.pem):

$ls *pem
ca-cert.pem  inter-cert.pem  leaf-server-cert.pem leaf-server1-cert.pem leaf-server2-cert.pem
ca-key.pem  inter-key.pem  leaf-server-key.pem leaf-server1-key.pem leaf-server2-key.pem

制作完证书后,我们就来看看日常使用最多的绑定单一TLS证书的情况。

2. 绑定单一TLS证书

做过web应用的读者,想必对绑定单一TLS证书的实现方式并不陌生。服务端只需要加载一对服务端私钥与公钥证书即可对外提供基于TLS的安全网络服务,这里一个echo服务为例,我们来看下服务端的代码:

// tls-certs-binding/bind_single_cert/sever/main.go


// 服务端
func startServer(certFile, keyFile string) {
 // 读取证书和密钥
 cert, err := tls.LoadX509KeyPair(certFile, keyFile)
 if err != nil {
  log.Fatal(err)
 }

 // 创建TLS配置
 config := &tls.Config{
  Certificates: []tls.Certificate{cert},
 }

 // 启动TLS服务器
 listener, err := tls.Listen("tcp", ":8443", config)
 if err != nil {
  log.Fatal(err)
 }
 defer listener.Close()

 log.Println("Server started")

 for {
  conn, err := listener.Accept()
  if err != nil {
   log.Println(err)
   continue
  }
  handleConnection(conn)
 }
}

func handleConnection(conn net.Conn) {
 defer conn.Close()
 // 处理连接...
 // 循环读取客户端的数据
 for {
  buf := make([]byte, 1024)
  n, err := conn.Read(buf)
  if err != nil {
   // 读取失败则退出
   return
  }

  // 回显数据给客户端
  s := string(buf[:n])
  fmt.Printf("recv data: %s\n", s)
  conn.Write(buf[:n])
 }
}

func main() {
 // 启动服务器
 startServer("leaf-server-cert.pem", "leaf-server-key.pem")
}

根据TLS的原理,客户端在与服务端的握手过程中,服务端会将服务端证书(leaf-server-cert.pem)发到客户端供后者验证,客户端使用服务器公钥证书校验服务器身份。这一过程的实质是客户端利用CA证书中的公钥或中间CA证书中的公钥对服务端证书中由CA私钥或中间CA私钥签名的数据进行验签

// tls-certs-binding/bind_single_cert/client/main.go

func main() {
 caCert, err := ioutil.ReadFile("inter-cert.pem")
 if err != nil {
  log.Fatal(err)
 }

 caCertPool := x509.NewCertPool()
 caCertPool.AppendCertsFromPEM(caCert)

 config := &tls.Config{
  RootCAs: caCertPool,
 }

 conn, err := tls.Dial("tcp", "server.com:8443", config)
 if err != nil {
  log.Fatal(err)
 }
 defer conn.Close()

 // 每秒发送信息
 ticker := time.NewTicker(time.Second)
 for range ticker.C {
  msg := "hello, tls"
  conn.Write([]byte(msg))

  // 读取回复
  buf := make([]byte, len(msg))
  conn.Read(buf)
  log.Println(string(buf))
 }

}

7c2de011538b0e02aa5a2bc7515e4dac.png

这里我们使用了签发了leaf-server-cert.pem证书的中间CA(inter-cert.pem)来验证服务端证书(leaf-server-cert.pem)的合法性,毫无疑问这是会成功的!

// server
$go run main.go
2023/10/05 22:49:17 Server started


// client

$go run main.go
2023/10/05 22:49:22 hello, tls
2023/10/05 22:49:23 hello, tls
... ...

注:运行上述代码之前,需修改/etc/hosts文件,添加server.com的IP为127.0.0.1。

不过要注意的是,在这里用CA根证书(ca-cert.pem)直接验证叶子证书(leaf-server-cert.pem)会失败,因为根证书不是叶子证书的直接签发者,必须通过验证证书链来建立根证书和叶子证书之间的信任链。

9e6f51b85c585e8d5ee2b309cac820fd.png

3. 证书链

实际生产中,服务器实体证书和根证书分别只有一张,但中间证书可以有多张,这些中间证书在客户端并不一定存在,这就可能导致客户端与服务端的连接无法建立。通过openssl命令也可以印证这一点:

// 在make_certs目录下

// CA根证书无法直接验证叶子证书
$openssl verify -CAfile ca-cert.pem leaf-server-cert.pem
leaf-server-cert.pem: O = Go Server, CN = server.com
error 20 at 0 depth lookup:unable to get local issuer certificate


// 证书链不完整,也无法验证
$openssl verify -CAfile inter-cert.pem leaf-server-cert.pem
leaf-server-cert.pem: O = Go Intermediate CA
error 2 at 1 depth lookup:unable to get issuer certificate

// 需要用完整证书链来验证
$openssl verify -CAfile ca-cert.pem -untrusted inter-cert.pem leaf-server-cert.pem
leaf-server-cert.pem: OK

为此在建连阶段,服务端不仅要将服务器实体证书发给客户端,还要发送完整的证书链(如下图所示)。

3e87ed1a42b7d305803214c5334c6531.png

证书链的最顶端是CA根证书,它的签名值是自己签名的,验证签名的公钥就包含在根证书中,根证书的签发者(Issuer)与使用者(Subject)是相同的。除了根证书,每个证书的签发者(Issuer)是它的上一级证书的使用者(Subject)。以上图为例,下列关系是成立的:

- ca-cert.pem的Issuer == ca-cert.pem的Subject
- inter1-cert.pem的Issuer == ca-cert.pem的Subject
- inter2-cert.pem的Issuer == inter1-cert.pem的Subject
... ...
- interN-cert.pem的Issuer == interN-1-cert.pem的Subject
- leaf-server-cert.pem的Issuer == interN-cert.pem的Subject

每张证书包含的重要信息是签发者(Issuer)、数字签名算法、签名值、使用者(Subject)域名、使用者公钥。除了根证书,每个证书(比如inter2-cert.pem证书)被它的上一级证书(比如inter1-cert.pem证书)对应的私钥签名,签名值包含在证书中,上一级证书包含的公钥可以用来验证该证书中的签名值(inter2-cert.pem证书可以用来验证inter1-cert.pem证书中的签名值)。

那么如何在服务端返回证书链呢?如何在客户端接收并验证证书链呢?我们来看下面示例。在这个示例中,客户端仅部署了根证书(ca-cert.pem),而服务端需要将服务证书与签发服务证书的中间CA证书以证书链的形式返回给客户端。

我们先来看服务端:

// tls-certs-binding/bind_single_cert/server-with-certs-chain/main.go

// 服务端
func startServer(certFile, keyFile string) {
 // 读取证书和密钥
 cert, err := tls.LoadX509KeyPair(certFile, keyFile)
 if err != nil {
  log.Fatal(err)
 }

 interCertBytes, err := os.ReadFile("inter-cert.pem")
 if err != nil {
  log.Fatal(err)
 }

 interCertblock, _ := pem.Decode(interCertBytes)

 // 将中间证书添加到证书链
 cert.Certificate = append(cert.Certificate, interCertblock.Bytes)

 // 创建TLS配置
 config := &tls.Config{
  Certificates: []tls.Certificate{cert},
 }

 // 启动TLS服务器
 listener, err := tls.Listen("tcp", ":8443", config)
 if err != nil {
  log.Fatal(err)
 }
 defer listener.Close()

 log.Println("Server started")

 for {
  conn, err := listener.Accept()
  if err != nil {
   log.Println(err)
   continue
  }
  handleConnection(conn)
 }
}

我们看到:服务端在加载完服务端证书后,又将中间CA证书inter-cert.pem attach到cert.Certificate,这样cert.Certificate中就构造出了一个证书链,而不单单是一个服务端证书了。

我们要注意证书链构造时的顺序,这里按照的是如下顺序构造证书链的:

- 服务端证书 (leaf certificate)
- 中间CA证书N
- 中间CA证书N-1
... ...
- 中间CA证书2
- 中间CA证书1

如果客户端没有根CA证书 (root certificate),在服务端构造证书链时,需要将根CA证书作为最后一个证书attach到证书链中。

下面则是客户端验证证书链的代码:

// tls-certs-binding/bind_single_cert/client-verify-certs-chain/main.go

func main() {
 // 加载ca-cert.pem
 caCertBytes, err := os.ReadFile("ca-cert.pem")
 if err != nil {
  log.Fatal(err)
 }

 caCertblock, _ := pem.Decode(caCertBytes)
 caCert, err := x509.ParseCertificate(caCertblock.Bytes)
 if err != nil {
  log.Fatal(err)
 }

 // 创建TLS配置
 config := &tls.Config{
  InsecureSkipVerify: true, // trigger to call VerifyPeerCertificate

  // 设置证书验证回调函数
  VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
   // 解析服务端返回的证书链(顺序:server-cert.pem, inter-cert.pem,inter-cert.pem's issuer...)

   var issuer *x509.Certificate
   var cert *x509.Certificate
   var err error

   if len(rawCerts) == 0 {
    return errors.New("no server certificate found")
   }

   issuer = caCert

   for i := len(rawCerts) - 1; i >= 0; i-- {
    cert, err = x509.ParseCertificate(rawCerts[i])
    if err != nil {
     return err
    }

    if !verifyCert(issuer, cert) {
     return errors.New("verifyCert failed")
    }

    issuer = cert
   }
   return nil
  },
 }

 conn, err := tls.Dial("tcp", "server.com:8443", config)
 if err != nil {
  log.Fatal(err)
 }
 defer conn.Close()

 // 每秒发送信息
 ticker := time.NewTicker(time.Second)
 for range ticker.C {
  msg := "hello, tls"
  conn.Write([]byte(msg))

  // 读取回复
  buf := make([]byte, len(msg))
  conn.Read(buf)
  log.Println(string(buf))
 }

}

// 验证cert是否是issuer的签发
func verifyCert(issuer, cert *x509.Certificate) bool {

 // 验证证书
 certPool := x509.NewCertPool()
 certPool.AddCert(issuer) // ok
 opts := x509.VerifyOptions{
  Roots: certPool,
 }
 _, err := cert.Verify(opts)
 return err == nil
}

从代码可以看到,我们需要将InsecureSkipVerify设置为true才能触发证书链的自定义校验逻辑(VerifyPeerCertificate)。在VerifyPeerCertificate中,我们先用ca根证书校验位于证书链最后的那个证书,验证成功后,用验证成功的证书验证倒数第二个证书,依次类推,知道全部证书都校验ok,说明证书链是可信任的。

服务端绑定一个证书或一套证书链是最简单的,也是最常见的方案,但在一些场景下,比如考虑支持多个域名、证书轮换等,TLS服务端可能需要绑定多个证书以满足要求。下面我们就来看看如何为TLS服务端绑定多个证书。

4. 绑定多个TLS证书

这个示例的证书绑定情况如下图:

d3fdce64e0cc784060601096500024c7.png

我们在服务端部署并绑定了三个证书,三个证书与域名的对应关系如下:

- 证书leaf-server-cert.pem 对应 server.com 
- 证书leaf-server1-cert.pem 对应 server1.com 
- 证书leaf-server2-cert.pem 对应 server2.com

注:在/etc/hosts中添加server1.com和server2.com对应的ip均为127.0.0.1。

// tls-certs-binding/bind_multi_certs/server/main.go

func main() {
 certFiles := []string{"leaf-server-cert.pem", "leaf-server1-cert.pem", "leaf-server2-cert.pem"}
 keyFiles := []string{"leaf-server-key.pem", "leaf-server1-key.pem", "leaf-server2-key.pem"}

 // 启动服务器
 startServer(certFiles, keyFiles)
}

// 服务端
func startServer(certFiles, keyFiles []string) {
 // 读取证书和密钥

 var certs []tls.Certificate
 for i := 0; i < len(certFiles); i++ {
  cert, err := tls.LoadX509KeyPair(certFiles[i], keyFiles[i])
  if err != nil {
   log.Fatal(err)
  }
  certs = append(certs, cert)
 }

 // 创建TLS配置
 config := &tls.Config{
  Certificates: certs,
 }

 // 启动TLS服务器
 listener, err := tls.Listen("tcp", ":8443", config)
 if err != nil {
  log.Fatal(err)
 }
 defer listener.Close()

 log.Println("Server started")

 for {
  conn, err := listener.Accept()
  if err != nil {
   log.Println(err)
   continue
  }
  handleConnection(conn)
 }
}

我们看到,绑定多个证书与绑定一个证书的原理是完全一样的,tls.Config的Certificates字段原本就是一个切片,可以容纳单个证书,也可以容纳证书链,容纳多个证书也不是问题。

客户端代码变化不大,我们仅是通过下面代码输出了服务端返回的证书的Subject.CN:

// tls-certs-binding/bind_multi_certs/client/main.go

// 解析连接的服务器证书
certs := conn.ConnectionState().PeerCertificates
if len(certs) > 0 {
    log.Println("Server CN:", certs[0].Subject.CommonName)
}

接下来我们通过client连接不同的域名,得到如下执行结果:

// 服务端
$go run main.go
2023/10/06 10:22:38 Server started

// 客户端
$go run main.go -server server.com:8443
2023/10/06 10:22:57 Server CN: server.com
2023/10/06 10:22:58 hello, tls

$go run main.go -server server1.com:8443
2023/10/06 10:23:02 Server CN: server1.com
2023/10/06 10:23:03 hello, tls
2023/10/06 10:23:04 hello, tls

$go run main.go -server server2.com:8443
2023/10/06 10:23:08 Server CN: server2.com
2023/10/06 10:23:09 hello, tls
... ...

我们看到,由于绑定多个域名对应的证书,程序可以支持访问不同域名的请求,并根据请求的域名,返回对应域名的证书。

5. 自定义证书选择绑定逻辑

无论是单一TLS证书、证书链还是多TLS证书,他们都有一个共同特点,那就是证书的绑定是事先已知的,是一种“静态”模式的绑定;有些场景下,服务端在初始化启动后并不会绑定某个固定的证书,而是根据客户端的连接需求以及特定规则在证书池中选择某个匹配的证书。在这种情况下,我们需要使用GetCertificate回调从自定义的证书池中选择匹配的证书,而不能在用上面示例中那种“静态”模式了。

我们来看一个自定义证书选择逻辑的示例,下面示意图展示了客户端和服务端的证书部署情况:

ed1f699d065cded7c1678df4a503b4fe.png

我们主要看一下服务端的代码逻辑变动:

// tls-certs-binding/bind_custom_logic/server/main.go

func startServer(certsPath string) {

    // 创建TLS配置
    config := &tls.Config{
        GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
            // 根据clientHello信息选择cert
            
            certFile := fmt.Sprintf("%s/leaf-%s-cert.pem", certsPath, info.ServerName[:len(info.ServerName)-4])
            keyFile := fmt.Sprintf("%s/leaf-%s-key.pem", certsPath, info.ServerName[:len(info.ServerName)-4])
            
            // 读取证书和密钥
            cert, err := tls.LoadX509KeyPair(certFile, keyFile)
            return &cert, err
        },  
    }   
 ... ...
}

我们看到: tls.Config我们建立了一个匿名函数赋值给了GetCertificate字段,该函数的实现逻辑就是根据客户端clientHello信息(tls握手时发送的信息)按照规则从证书池目录中查找并加载对应的证书与其私钥信息。示例使用ServerName来查找带有同名信息的证书。

例子的运行结果与上面的示例都差不多,这里就不赘述了。

利用这种动态的证书选择逻辑,我们还可以实现通过执行外部命令来获取证书、从数据库加载证书等。

6. 小结

通过本文的介绍,我们全面了解了在Go服务端绑定单个、多个TLS证书的各种方式。我们首先介绍了生成自签名证书的方法,这为我们的示例程序奠定了基础。然后我们详细探讨了绑定单证书、证书链、多证书、定制从证书池取特定证书的逻辑等不同机制的用法、优劣势和适用场景。同时,在介绍每种用法时,我们都用代码示例进一步解释了这些绑定方式的具体实现流程。

单证书TLS简单易理解,运行性能优异。多证书TLS在提高性能、安全性、便利管理等方面有着重要意义。而自定义证书选取逻辑则更加灵活。通过综合运用各种绑定机制,可以使我们的Go语言服务器端更加强大和灵活。

本文示例所涉及的Go源码可以在这里[7]下载。

注:代码仓库中的证书和key文件有效期为一年,大家如发现证书已经过期,可以在make_certs目录下重新生成各种证书和私钥并copy到对应的其他目录中去。

7. 参考资料

  • 《深入浅出 HTTPS:从原理到实战》[8] - https://book.douban.com/subject/30250772/

  • Certificate chains and cross-certification[9] - https://en.wikipedia.org/wiki/X.509#Certificate_chains_and_cross-certification


“Gopher部落”知识星球[10]旨在打造一个精品Go学习和进阶社群!高品质首发Go技术文章,“三天”首发阅读权,每年两期Go语言发展现状分析,每天提前1小时阅读到新鲜的Gopher日报,网课、技术专栏、图书内容前瞻,六小时内必答保证等满足你关于Go语言生态的所有需求!2023年,Gopher部落将进一步聚焦于如何编写雅、地道、可读、可测试的Go代码,关注代码质量并深入理解Go核心技术,并继续加强与星友的互动。欢迎大家加入!

79a807afe290e92218e0e85d159e5513.jpeg29b248a2865f61fd9cb2959f6c098214.png

afd987baeadd1f1024ad54b9ab8e02b1.pngf15c45cdfbceb9790d3341b6d50ca0af.jpeg

著名云主机服务厂商DigitalOcean发布最新的主机计划,入门级Droplet配置升级为:1 core CPU、1G内存、25G高速SSD,价格5$/月。有使用DigitalOcean需求的朋友,可以打开这个链接地址[11]:https://m.do.co/c/bff6eed92687 开启你的DO主机之路。

Gopher Daily(Gopher每日新闻) - https://gopherdaily.tonybai.com

我的联系方式:

  • 微博(暂不可用):https://weibo.com/bigwhite20xx

  • 微博2:https://weibo.com/u/6484441286

  • 博客:tonybai.com

  • github: https://github.com/bigwhite

  • Gopher Daily归档 - https://github.com/bigwhite/gopherdaily

45fba7c048b05b5d36a75c7d291cb3b3.jpeg

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

参考资料

[1] 

TLS(Transport Layer Security): https://tonybai.com/2023/01/13/go-and-tls13

[2] 

一个TLS服务器通常只绑定一个证书: https://tonybai.com/2015/04/30/go-and-https

[3] 

Filippo Valsorda: https://filippo.io/

[4] 

TLS使用方法: https://tonybai.com/2015/04/30/go-and-https

[5] 

《Go语言第一课》: http://gk.link/a/10AVZ

[6] 

openssl: https://www.openssl.org

[7] 

这里: https://github.com/bigwhite/experiments/tree/master/tls-certs-binding

[8] 

《深入浅出 HTTPS:从原理到实战》: https://book.douban.com/subject/30250772/

[9] 

Certificate chains and cross-certification: https://en.wikipedia.org/wiki/X.509#Certificate_chains_and_cross-certification

[10] 

“Gopher部落”知识星球: https://public.zsxq.com/groups/51284458844544

[11] 

链接地址: https://m.do.co/c/bff6eed92687

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1095307.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GEE 18:基于GEE平台的土地荒漠化监测与分析【论文复现】

Desertification 1. 研究背景1.1 参考论文1.2 参数获取1.2.1 NDVI1.2.2 Albedo1.2.3 Normalizing indices1.2.4 Calculating the quantitative relationship1.2.5 Calculating DDI2. GEE2.1 数据2.2 GEE code2.2.1 Study region2.2.2 Reomove cloud for Landsat-82.2.3 Calcula…

Rollup failed to resolve import

问题描述 我在打包vue3的项目的时候报了以下错误 问题原因 打包时&#xff0c;静态资源目录没有识别出来&#xff0c;导致打包终止并报错 问题解决 需要把路径改为跟路径&#xff0c;就可以打包啦 错误资源路径写法 <img src"src/assets/image/底部导航背景/book2…

UE5蓝图-事件、函数、事件分发器

UE5蓝图中的事件、函数、事件分发器理解及学习 1 事件 蓝图的事件在事件图表中。 事件可以进行自定义。 1.1 首先自定义一个事件HelloUE 1.2 为事件指定具体的执行体 1.3 运行事件 1.4 绑定事件到 Actor被点击 先进行事件绑定&#xff0c;绑定完成后&#xff0c;BBBB被点击…

JAVA总结01

1.变量在定义的时候可以不赋初始值&#xff0c;但在使用的时候变量必须有值 2.数据类型 数据类型字节数整型byte1short2int4long8浮点型float4double8字符型char2布尔型boolean1 浮点数默认都是double类型 上面代码报错的原因就是5.0是double类型&#xff0c;不是float类型 当…

【随笔】论多线程CPU离线渲染器的实现:A CPU BASED OFFLINE RENDERING ENGINE

前言 小熊挺喜欢玩游戏的&#xff0c;对于游戏画面有所追求&#xff0c;记得高中第一次玩战地的时候&#xff0c;惊叹于画面细腻的表现&#xff0c;并且还能开坦克车&#xff0c;这样的事情深深吸引了我。我是一个画面党&#xff0c;为了追求更好的画质表现我开始研究设置面板…

windows server 2012 查看已打了哪些补丁

打开控制面板 点击卸载程序 点击 查看已安装的更新 下图是已安装的补丁

Java反射调用jar包

反射作为托管语言的特性&#xff0c;很多框架都依赖反射来实现容器和面向接口编程&#xff0c;对架构程序很重要&#xff0c;首先试验Java的反射部分。 首先创建一个ZLZJar的工程&#xff0c;让他打包成jar包&#xff0c;代码如下 public class Test {//加两个整数public int…

WAF绕过-漏洞发现之代理池指纹探针 47

工具 工具分为综合性的&#xff0c;有awvs&#xff0c;xray&#xff0c;单点的比如wpscan专门扫描wordpress的。而我们使用工具就可能会触发waf&#xff0c; 触发点 第一个就是扫描速度&#xff0c;太快了&#xff0c;可以通过演示&#xff0c;开代理池&#xff0c;白名单绕…

美国市场三星手机超苹果 中国第一属华为

报告显示&#xff0c;截至5月份的三个月&#xff0c;iOS系统在美国、澳大利亚以及日本表现不俗。Android系统份额则在英国、德国以及法国实现增长。在中国城市地区&#xff0c;iOS份额同比基本持平&#xff0c;而Android份额则达到80.5%&#xff0c;同比增长1个百分点。 三星在…

2023-2024-1 for循环-1(1-14)

7-1 求N分之一序列前N项和 本题要求编写程序&#xff0c;计算序列 1 1/2 1/3 ... 的前N项之和。 输入格式: 输入在一行中给出一个正整数N。 输出格式: 在一行中按照“sum S”的格式输出部分和的值S&#xff0c;精确到小数点后6位。题目保证计算结果不超过双精度范围。…

主流接口测试框架对比

公司计划系统的开展接口自动化测试&#xff0c;需要我这边调研一下主流的接口测试框架给后端测试&#xff08;主要测试接口&#xff09;的同事介绍一下每个框架的特定和使用方式。后端同事根据他们接口的特点提出一下需求&#xff0c;看哪个框架更适合我们。 需求 1、接口编写…

如何设计一个自动化测试平台

平台解决了什么问题 一般在企业内&#xff0c;技术团队如果规模比较小&#xff0c;很少会专门投入资源去做平台化的事情&#xff0c;特别是测试团队&#xff0c;无论是成本预算还是技术能力&#xff0c;先天技术能力不足&#xff0c;后天可投入的资源缺乏。 而平台的特点在于…

杭州亚运会用到哪些黑科技?

Spring实战系列文章&#xff1a; Spring实战 | Spring AOP核心秘笈之葵花宝典 Spring实战 | Spring IOC不能说的秘密&#xff1f; 国庆中秋特辑系列文章&#xff1a; 国庆中秋特辑&#xff08;八&#xff09;Spring Boot项目如何使用JPA 国庆中秋特辑&#xff08;七&#…

Studio one 6.5中文直装版最新版,一键安装丨无套路分享丨宿主软件丨机架直播

Studio One 6.5 具有最先进的沉浸式混音和集成的空间音频制作工作流程。还有新的插件&#xff0c;改进的乐谱功能和对 DAWproject 的支持。 PreSonus 的“.5”更新通常都有比较大的变化&#xff0c;这次也不例外。Studio One 6.5 增加了一种全新的工作方式&#xff0c;完全集成…

博弈论——博弈信息结构

博弈信息结构 0 引言 在一个博弈构成中&#xff0c;博弈信息结构是不可或缺要素。博弈信息&#xff0c;顾名思义&#xff0c;就是在博弈中&#xff0c;博弈方对于信息的了解。知己知彼&#xff0c;百战不殆。和短兵相接的战争一样&#xff0c;只有充分了解自己的优劣势&#x…

python---求面积练习:提示用户输入圆的半径,根据公式S = πr2求圆的面积

求面积练习&#xff1a;提示用户输入圆的半径&#xff0c;根据公式S πr2求圆的面积 r float(input(‘请输入要计算圆的半径&#xff1a;’)) PI 3.14 s PI * r ** 2 print(f’圆的面积为&#xff1a;{s}) r float(input(‘请输入要计算圆的半径&#xff1a;’)) ------…

NetSuite ERP系统健康检查

这个题目来自最近的一个项目感受&#xff0c;“上线即停滞”。这是在中小型企业十分普遍的一个情况&#xff0c;一旦上线后&#xff0c;基本上信息化的建设就停止了。这是一个中小企业信息化的一个特点&#xff0c;因为其IT力量比较弱&#xff0c;所以在信息化的推动中缺乏话语…

蓝桥杯(等差素数列,C++)

思路&#xff1a; 1、因为找的是长度为10&#xff0c;且公差最小的等差素数列&#xff0c;直接用枚举即可。 2、枚举用三重循环&#xff0c;第一重枚举首项&#xff0c;第二重枚举公差&#xff0c;第三重因为首项算一个&#xff0c;所以枚举九个等差素数。 代码&#xff1a;…

异步使用langchain

文章目录 一.先利用langchain官方文档的AI功能问问二.langchain async api三.串行&#xff0c;异步速度比较 一.先利用langchain官方文档的AI功能问问 然后看他给的 Verified Sources 这个页面里面虽然有些函数是异步函数&#xff0c;但是并非专门讲解异步的 二.langchain asy…

08Maven中的继承和聚合的作用

Maven中的继承 实际开发中对一个比较大型的项目进行了模块拆分 , 一个project下面创建了很多个modul, 每一个module都需要配置自己的依赖信息 开发中使用的同一个框架内的不同jar包&#xff0c;它们应该是同一个版本&#xff0c;所以整个项目中使用的框架版本需要统一 传统方…