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()
: 创建一个新的、空的 CertPoolcertPool.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,共使用了三个 Config 配置项:
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 安全认证