Golang gRPC笔记03 基于 CA 的 TLS 证书认证

一、 基于 CA 的证书生成

1. 生成 CA 证书

为了保证证书的可靠性和有效性,在这里可引入 CA 颁发的根证书的概念。
根证书(root certificate)是属于根证书颁发机构(CA)的公钥证书。我们可以通过验证 CA 的签名从而信任 CA ,任何人都可以得到 CA 的证书(含公钥),用以验证它所签发的证书(客户端、服务端)

生成私钥:
openssl genrsa -out ca.key 2048
生成公钥:
openssl req -new -x509 -days 7200 -key ca.key -out ca.pem

填写信息:
Country Name (2 letter code) [XX]:cn
State or Province Name (full name) []:shanghai
Locality Name (eg, city) [Default City]:shanghai
Organization Name (eg, company) [Default Company Ltd]:hellokitty
Organizational Unit Name (eg, section) []:hellokitty
Common Name (eg, your name or your server's hostname) []:localhost
Email Address []:

2. 生成 server 端签名证书

生成 server 端私钥:
openssl genrsa -out server.key 2048
生成 server 端证书签名请求文件(CSR):
openssl req -new -key server.key -out server.csr

填写信息:
Country Name (2 letter code) [XX]:cn
State or Province Name (full name) []:shanghai
Locality Name (eg, city) [Default City]:shanghai
Organization Name (eg, company) [Default Company Ltd]:hellokitty
Organizational Unit Name (eg, section) []:hellokitty
Common Name (eg, your name or your server's hostname) []:localhost
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

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

基于 CA 签发 server 端证书:
openssl x509 -req -sha256 -CA ca.pem -CAkey ca.key -CAcreateserial -days 3650 -in server.csr -out server.pem

3. 生成 client 端签名证书

生成 client 端私钥:
openssl genrsa -out client.key 2048
生成 client 端证书签名请求文件(CSR):
openssl req -new -key client.key -out client.csr

填写信息:
Country Name (2 letter code) [XX]:cn
State or Province Name (full name) []:shanghai
Locality Name (eg, city) [Default City]:shanghai
Organization Name (eg, company) [Default Company Ltd]:hellokitty
Organizational Unit Name (eg, section) []:hellokitty
Common Name (eg, your name or your server's hostname) []:localhost
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
基于 CA 签发 client 端证书:
openssl x509 -req -sha256 -CA ca.pem -CAkey ca.key -CAcreateserial -days 3650 -in client.csr -out client.pem
整理文件:

在此过程中,一共生成了如下文件

conf/
├── keys/
    |—— ca.key  // ca私钥【保密】
    |—— ca.pem  // ca公钥
    |—— ca.srl  // 签名期间生成【保密】
    |—— client/
        |—— client.csr   // 证书签名请求文件【可删除】
        |—— client.key   // 私钥
        |—— client.pem   // 公钥
    |—— server/
        |—— server.csr   // 【可删除】
        |—— server.key
        |—— server.pem

其中一些文件是不应该出现在仓库内,应当保密或删除的。但为了真实演示所以保留着

二、 完成项目编码

项目目录:

grpc_demo/
|—— demo03/
    |—— client/
        |—— client.go   // 客户端
    |—— conf/
        |—— keys/
            |—— ca.key
            |—— ca.pem
            |—— ca.srl
            |—— client/
                |—— client.csr
                |—— client.key
                |—— client.pem
            |—— server/
                |—— server.csr
                |—— server.key
                |—— server.pem
    |—— pkg/
        |—— util/
            |—— tls_support.go
    |—— proto/
        |—— prod/
            |—— prod.proto
            |—— prod.pb.go
    |—— server/
        |—— server.go   // 服务端

.proto文件:prod.proto

无变动

util: tls_support.go

在 pkg 下新建 util 目录,新建tls_support.go文件,用于辅助构建 TLS 认证选项

package util

import (
   "crypto/tls"
   "crypto/x509"
   "fmt"
   "io/ioutil"

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

// 封装 TLS 认证服务端选项
func GetTLSServerOption(serverCertFile, serverKeyFile string) (grpc.ServerOption, error) {
   creds, err := credentials.NewServerTLSFromFile(serverCertFile, serverKeyFile)
   if err != nil {
   	return nil, fmt.Errorf("credentials.NewServerTLSFromFile err: %v", err)
   }

   return grpc.Creds(creds), nil
}

// 封装 TLS 认证客户端选项
func GetTLSDialOption(serverCertFile, certServerName string) (grpc.DialOption, error) {
   creds, err := credentials.NewClientTLSFromFile(serverCertFile, certServerName)
   if err != nil {
   	return nil, fmt.Errorf("credentials.NewClientTLSFromFile err: %v", err)
   }

   return grpc.WithTransportCredentials(creds), nil
}

// 封装基于 CA 的 TLS 认证服务端选项
func GetCATLSServerOption(serverCertFile, serverKeyFile, caFile string) (grpc.ServerOption, error) {
   // 从证书相关文件中读取和解析信息,得到证书公钥、密钥对
   cert, err := tls.LoadX509KeyPair(serverCertFile, serverKeyFile)
   if err != nil {
   	return nil, fmt.Errorf("tls.LoadX509KeyPair err: %v", err)
   }

   // 创建一个新的、空的 CertPool,并尝试解析 PEM 编码的证书,解析成功会将其加到 CertPool 中
   certPool := x509.NewCertPool()
   ca, err := ioutil.ReadFile(caFile)
   if err != nil {
   	return nil, fmt.Errorf("ioutil.ReadFile err: %v", err)
   }

   if ok := certPool.AppendCertsFromPEM(ca); !ok {
   	return nil, fmt.Errorf("certPool.AppendCertsFromPEM err")
   }

   // 构建基于 TLS 的 TransportCredentials 选项
   creds := credentials.NewTLS(&tls.Config{
   	// 设置证书链,允许包含一个或多个
   	Certificates: []tls.Certificate{cert},
   	// 要求必须校验客户端的证书
   	ClientAuth: tls.RequireAndVerifyClientCert,
   	// 设置根证书的集合,校验方式使用 ClientAuth 中设定的模式
   	ClientCAs: certPool,
   })

   return grpc.Creds(creds), nil
}

// 封装基于 CA 的 TLS 认证客户端选项
func GetCATLSDialOption(clientCertFile, clientKeyFile, caFile, certServerName string) (grpc.DialOption, error) {
   cert, err := tls.LoadX509KeyPair(clientCertFile, clientKeyFile)
   if err != nil {
   	return nil, fmt.Errorf("tls.LoadX509KeyPair err: %v", err)
   }

   certPool := x509.NewCertPool()
   ca, err := ioutil.ReadFile(caFile)
   if err != nil {
   	return nil, fmt.Errorf("ioutil.ReadFile err: %v", err)
   }

   if ok := certPool.AppendCertsFromPEM(ca); !ok {
   	return nil, fmt.Errorf("certPool.AppendCertsFromPEM err")
   }

   // 构建基于 TLS 的 TransportCredentials 选项
   // 在 Client 请求 Server 端时,Client 端会使用根证书和 ServerName 去对 Server 端进行校验
   creds := credentials.NewTLS(&tls.Config{
   	Certificates: []tls.Certificate{cert},
   	ServerName:   certServerName,
   	RootCAs:      certPool,
   })

   return grpc.WithTransportCredentials(creds), nil
}
  • tls.LoadX509KeyPair(): 从证书相关文件中读取和解析信息,得到证书公钥、密钥对
  • x509.NewCertPool(): 创建一个新的、空的 CertPool
  • certPool.AppendCertsFromPEM(): 尝试解析所传入的 PEM 编码的证书。如果解析成功会将其加到 CertPool 中,便于后面的使用
  • credentials.NewTLS(): 构建基于 TLS 的 TransportCredentials 选项
  • tls.Config: Config 结构用于配置 TLS 客户端或服务器
    • 在 Server,共使用了三个 Config 配置项:
      • Certificates:设置证书链,允许包含一个或多个
      • ClientAuth:要求必须校验客户端的证书。可以根据实际情况选用以下参数:
        • NoClientCertClientAuthType = iota
        • RequestClientCert
        • RequireAnyClientCert
        • VerifyClientCertIfGiven
        • RequireAndVerifyClientCert
      • ClientCAs:设置根证书的集合,校验方式使用 ClientAuth 中设定的模式
    • 在 Client, 大部分与 Server 一致,不同点的地方是,在 Client 请求 Server 端时,Client 端会使用根证书和 ServerName 去对 Server 端进行校验
      • 简单流程如下:
      • 1.Client 通过请求得到 Server 端的证书
      • 2.使用 CA 认证的根证书对 Server 端的证书进行可靠性、有效性等校验
      • 3.校验 ServerName 是否可用、有效
    • 在设置了 tls.RequireAndVerifyClientCert 模式的情况下,Server 也会使用 CA 认证的根证书对 Client 端的证书进行可靠性、有效性等校验。也就是两边都会进行校验,极大的保证了安全性

server端:server.go

package main

import (
	"context"
	"grpc_demo/demo03/pkg/util"
	prodpb "grpc_demo/demo03/proto/prod"
	"log"
	"net"

	"google.golang.org/grpc"
)

type ProdService struct{}

func (p ProdService) GetProdStock(_ context.Context, prodReq *prodpb.ProdRequest) (*prodpb.ProdResponse, error) {
	log.Printf("请求参数:ProdRequest.ProdId=%d", prodReq.ProdId)
	return &prodpb.ProdResponse{ProdStock: 3008}, nil
}

// 生成ProdService
func CreateProdService() ProdService {
	return ProdService{}
}

const (
	// Address gRPC服务地址
	Address = "127.0.0.1:8899"
)

func main() {
	//serverOpt, err := util.GetTLSServerOption("demo02/conf/keys/server.pem", "demo02/conf/keys/server.key")
	//if err != nil {
	//	log.Fatalf("加载服务端 TLS 凭证失败,err=%v", err)
	//}

	serverOpt, err := util.GetCATLSServerOption("demo03/conf/keys/server/server.pem", "demo03/conf/keys/server/server.key", "demo03/conf/keys/ca.pem")
	if err != nil {
		log.Fatalf("加载服务端 TLS 凭证失败,err=%v", err)
	}

	// 1. 创建 gRPC Server 的实例对象,并开启TLS认证
	rpcServer := grpc.NewServer(serverOpt)
	// 2.gRPC Server 内部服务和路由的注册
	prodpb.RegisterProdServiceServer(rpcServer, CreateProdService())

	// 3. 监听指定 TCP 端口,用于接受客户端请求
	listener, err := net.Listen("tcp", Address)
	if err != nil {
		panic("net.Listen err: " + err.Error())
	}

	log.Printf("Listening on %s\n", Address)
	// 4. Serve() 调用服务器以执行阻塞等待,直到进程被终止或被 Stop() 调用
	log.Fatal(rpcServer.Serve(listener))
}

client端:client.go

package main

import (
	"context"
	prodpb "grpc_demo/demo02/proto/prod"
	"grpc_demo/demo03/pkg/util"
	"log"

	"google.golang.org/grpc"
)

const (
	// Address gRPC服务地址
	Address = "127.0.0.1:8899"
)

func main() {
	//dialOpt, err := util.GetTLSDialOption("demo02/conf/keys/server.pem", "localhost")
	//if err != nil {
	//	log.Fatalf("加载客户端 TLS 凭证失败,err=%v", err)
	//}

	dialOpt, err := util.GetCATLSDialOption("demo03/conf/keys/client/client.pem", "demo03/conf/keys/client/client.key", "demo03/conf/keys/ca.pem", "localhost")
	if err != nil {
		log.Fatalf("加载客户端 TLS 凭证失败,err=%v", err)
	}

	// 连接 rpc 服务器
	conn, err := grpc.Dial(Address, dialOpt)
	if err != nil {
		panic("grpc.Dial err: " + err.Error())
	}
	defer conn.Close()

	// 初始化客户端
	client := prodpb.NewProdServiceClient(conn)
	resp, err := client.GetProdStock(context.Background(), &prodpb.ProdRequest{ProdId: 33333})
	if err != nil {
		log.Print("调用失败,err=", err)
		return
	}
	log.Printf("%+v \n", resp)
}

验证:

启动 Server:

cd demo03/server
go run server.go

Listening on 127.0.0.1:8899

启动 Client:

cd demo03/client
go run client.go

prod_stock:3008 

成功获取数据, 完成基于 CA 的 TLS 安全认证

posted @ 2020-07-11 17:38  朱一一  阅读(1005)  评论(0)    收藏  举报