63

Go gRPC 系列五:基于 CA 的 TLS 证书校验

 3 years ago
source link: https://www.tuicool.com/articles/RNRjeqz
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

大家好,我是煎鱼,在上一章节中,我们提出了一个问题。就是如何保证证书的可靠性和有效性?你如何确定你 Server、Client 的证书是对的呢?

CA

为了保证证书的可靠性和有效性,在这里可引入 CA 颁发的根证书的概念。其遵守 X.509 标准

根证书

根证书(root certificate)是属于根证书颁发机构(CA)的公钥证书。我们可以通过验证 CA 的签名从而信任 CA ,任何人都可以得到 CA 的证书(含公钥),用以验证它所签发的证书(客户端、服务端)

它包含的文件如下:

  • 公钥
  • 密钥

生成 Key

openssl genrsa -out ca.key 2048
复制代码

生成密钥

openssl req -new -x509 -days 7200 -key ca.key -out ca.pem
复制代码

填写信息

Country Name (2 letter code) []:
State or Province Name (full name) []:
Locality Name (eg, city) []:
Organization Name (eg, company) []:
Organizational Unit Name (eg, section) []:
Common Name (eg, fully qualified host name) []:go-grpc-example
Email Address []:
复制代码

Server

生成 CSR

openssl req -new -key server.key -out server.csr
复制代码

填写信息

Country Name (2 letter code) []:
State or Province Name (full name) []:
Locality Name (eg, city) []:
Organization Name (eg, company) []:
Organizational Unit Name (eg, section) []:
Common Name (eg, fully qualified host name) []:go-grpc-example
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
复制代码

CSR 是 Cerificate Signing Request 的英文缩写,为证书请求文件。主要作用是 CA 会利用 CSR 文件进行签名使得攻击者无法伪装或篡改原有证书

基于 CA 签发

openssl x509 -req -sha256 -CA ca.pem -CAkey ca.key -CAcreateserial -days 3650 -in server.csr -out server.pem
复制代码

Client

生成 Key

openssl ecparam -genkey -name secp384r1 -out client.key
复制代码

生成 CSR

openssl req -new -key client.key -out client.csr
复制代码

基于 CA 签发

openssl x509 -req -sha256 -CA ca.pem -CAkey ca.key -CAcreateserial -days 3650 -in client.csr -out client.pem
复制代码

整理目录

至此我们生成了一堆文件,请按照以下目录结构存放:

$ tree conf 
conf
├── ca.key
├── ca.pem
├── ca.srl
├── client
│   ├── client.csr
│   ├── client.key
│   └── client.pem
└── server
    ├── server.csr
    ├── server.key
    └── server.pem
复制代码

另外有一些文件是不应该出现在仓库内,应当保密或删除的。但为了真实演示所以保留着(敲黑板)

gRPC

接下来将正式开始针对 gRPC 进行编码,改造上一章节的代码。目标是基于 CA 进行 TLS 认证

Server

package main

import (
	"context"
	"log"
	"net"
	"crypto/tls"
	"crypto/x509"
	"io/ioutil"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"

	pb "github.com/EDDYCJY/go-grpc-example/proto"
)

...

const PORT = "9001"

func main() {
	cert, err := tls.LoadX509KeyPair("../../conf/server/server.pem", "../../conf/server/server.key")
	if err != nil {
		log.Fatalf("tls.LoadX509KeyPair err: %v", err)
	}

	certPool := x509.NewCertPool()
	ca, err := ioutil.ReadFile("../../conf/ca.pem")
	if err != nil {
		log.Fatalf("ioutil.ReadFile err: %v", err)
	}

	if ok := certPool.AppendCertsFromPEM(ca); !ok {
		log.Fatalf("certPool.AppendCertsFromPEM err")
	}

	c := credentials.NewTLS(&tls.Config{
		Certificates: []tls.Certificate{cert},
		ClientAuth:   tls.RequireAndVerifyClientCert,
		ClientCAs:    certPool,
	})

	server := grpc.NewServer(grpc.Creds(c))
	pb.RegisterSearchServiceServer(server, &SearchService{})

	lis, err := net.Listen("tcp", ":"+PORT)
	if err != nil {
		log.Fatalf("net.Listen err: %v", err)
	}

	server.Serve(lis)
}
复制代码
  • tls.LoadX509KeyPair():从证书相关文件中 读取解析 信息,得到证书公钥、密钥对
func LoadX509KeyPair(certFile, keyFile string) (Certificate, error) {
	certPEMBlock, err := ioutil.ReadFile(certFile)
	if err != nil {
		return Certificate{}, err
	}
	keyPEMBlock, err := ioutil.ReadFile(keyFile)
	if err != nil {
		return Certificate{}, err
	}
	return X509KeyPair(certPEMBlock, keyPEMBlock)
}
复制代码
  • x509.NewCertPool():创建一个新的、空的 CertPool
  • certPool.AppendCertsFromPEM():尝试解析所传入的 PEM 编码的证书。如果解析成功会将其加到 CertPool 中,便于后面的使用
  • credentials.NewTLS:构建基于 TLS 的 TransportCredentials 选项
  • tls.Config:Config 结构用于配置 TLS 客户端或服务器

在 Server,共使用了三个 Config 配置项:

(1)Certificates:设置证书链,允许包含一个或多个

(2)ClientAuth:要求必须校验客户端的证书。可以根据实际情况选用以下参数:

const (
	NoClientCert ClientAuthType = iota
	RequestClientCert
	RequireAnyClientCert
	VerifyClientCertIfGiven
	RequireAndVerifyClientCert
)
复制代码

(3)ClientCAs:设置根证书的集合,校验方式使用 ClientAuth 中设定的模式

Client

package main

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"io/ioutil"
	"log"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"

	pb "github.com/EDDYCJY/go-grpc-example/proto"
)

const PORT = "9001"

func main() {
	cert, err := tls.LoadX509KeyPair("../../conf/client/client.pem", "../../conf/client/client.key")
	if err != nil {
		log.Fatalf("tls.LoadX509KeyPair err: %v", err)
	}

	certPool := x509.NewCertPool()
	ca, err := ioutil.ReadFile("../../conf/ca.pem")
	if err != nil {
		log.Fatalf("ioutil.ReadFile err: %v", err)
	}

	if ok := certPool.AppendCertsFromPEM(ca); !ok {
		log.Fatalf("certPool.AppendCertsFromPEM err")
	}

	c := credentials.NewTLS(&tls.Config{
		Certificates: []tls.Certificate{cert},
		ServerName:   "go-grpc-example",
		RootCAs:      certPool,
	})

	conn, err := grpc.Dial(":"+PORT, grpc.WithTransportCredentials(c))
	if err != nil {
		log.Fatalf("grpc.Dial err: %v", err)
	}
	defer conn.Close()

	client := pb.NewSearchServiceClient(conn)
	resp, err := client.Search(context.Background(), &pb.SearchRequest{
		Request: "gRPC",
	})
	if err != nil {
		log.Fatalf("client.Search err: %v", err)
	}

	log.Printf("resp: %s", resp.GetResponse())
}
复制代码

在 Client 中绝大部分与 Server 一致,不同点的地方是,在 Client 请求 Server 端时,Client 端会使用根证书和 ServerName 去对 Server 端进行校验

简单流程大致如下:

  1. Client 通过请求得到 Server 端的证书
  2. 使用 CA 认证的根证书对 Server 端的证书进行可靠性、有效性等校验
  3. 校验 ServerName 是否可用、有效

当然了,在设置了 tls.RequireAndVerifyClientCert 模式的情况下,Server 也会使用 CA 认证的根证书对 Client 端的证书进行可靠性、有效性等校验。也就是两边都会进行校验,极大的保证了安全性 :+1:

验证

重新启动 server.go 和执行 client.go,查看响应结果是否正常

总结

在本章节,我们使用 CA 颁发的根证书对客户端、服务端的证书进行了签发。进一步的提高了两者的通讯安全

这回是真的大功告成了!

如果有任何疑问或错误,欢迎在 issues 进行提问或给予修正意见,如果喜欢或对你有所帮助,欢迎 Star ,对作者是一种鼓励和推进。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK