「连载五」基于 CA 的 TLS 证书认证

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

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

    它包含的文件如下:

    • 公钥
    • 密钥

    生成 Key

    生成密钥

    填写信息

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

    Server

    生成 CSR

    1. openssl req -new -key server.key -out server.csr
    填写信息

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

    基于 CA 签发

    1. openssl x509 -req -sha256 -CA ca.pem -CAkey ca.key -CAcreateserial -days 3650 -in server.csr -out server.pem

    生成 Key

    1. openssl ecparam -genkey -name secp384r1 -out client.key

    生成 CSR

    1. openssl req -new -key client.key -out client.csr

    基于 CA 签发

    整理目录

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

    1. $ tree conf
    2. conf
    3. ├── ca.key
    4. ├── ca.pem
    5. ├── ca.srl
    6. ├── client
    7. ├── client.csr
    8. ├── client.key
    9. └── client.pem
    10. └── server
    11. ├── server.csr
    12. ├── server.key
    13. └── server.pem

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

    1. package main
    2. import (
    3. "context"
    4. "log"
    5. "net"
    6. "crypto/tls"
    7. "crypto/x509"
    8. "io/ioutil"
    9. "google.golang.org/grpc"
    10. "google.golang.org/grpc/credentials"
    11. pb "github.com/EDDYCJY/go-grpc-example/proto"
    12. )
    13. ...
    14. const PORT = "9001"
    15. func main() {
    16. cert, err := tls.LoadX509KeyPair("../../conf/server/server.pem", "../../conf/server/server.key")
    17. if err != nil {
    18. }
    19. certPool := x509.NewCertPool()
    20. ca, err := ioutil.ReadFile("../../conf/ca.pem")
    21. if err != nil {
    22. log.Fatalf("ioutil.ReadFile err: %v", err)
    23. }
    24. if ok := certPool.AppendCertsFromPEM(ca); !ok {
    25. log.Fatalf("certPool.AppendCertsFromPEM err")
    26. }
    27. c := credentials.NewTLS(&tls.Config{
    28. Certificates: []tls.Certificate{cert},
    29. ClientAuth: tls.RequireAndVerifyClientCert,
    30. ClientCAs: certPool,
    31. })
    32. server := grpc.NewServer(grpc.Creds(c))
    33. pb.RegisterSearchServiceServer(server, &SearchService{})
    34. lis, err := net.Listen("tcp", ":"+PORT)
    35. if err != nil {
    36. log.Fatalf("net.Listen err: %v", err)
    37. }
    38. server.Serve(lis)
    39. }
    • tls.LoadX509KeyPair():从证书相关文件中读取解析信息,得到证书公钥、密钥对
    1. func LoadX509KeyPair(certFile, keyFile string) (Certificate, error) {
    2. certPEMBlock, err := ioutil.ReadFile(certFile)
    3. if err != nil {
    4. return Certificate{}, err
    5. }
    6. keyPEMBlock, err := ioutil.ReadFile(keyFile)
    7. if err != nil {
    8. return Certificate{}, err
    9. }
    10. return X509KeyPair(certPEMBlock, keyPEMBlock)
    11. }
    • x509.NewCertPool():创建一个新的、空的 CertPool
    • certPool.AppendCertsFromPEM():尝试解析所传入的 PEM 编码的证书。如果解析成功会将其加到 CertPool 中,便于后面的使用
    • credentials.NewTLS:构建基于 TLS 的 TransportCredentials 选项
    • tls.Config:Config 结构用于配置 TLS 客户端或服务器

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

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

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

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

    Client

    1. import (
    2. "context"
    3. "crypto/tls"
    4. "crypto/x509"
    5. "io/ioutil"
    6. "log"
    7. "google.golang.org/grpc"
    8. "google.golang.org/grpc/credentials"
    9. pb "github.com/EDDYCJY/go-grpc-example/proto"
    10. )
    11. const PORT = "9001"
    12. func main() {
    13. cert, err := tls.LoadX509KeyPair("../../conf/client/client.pem", "../../conf/client/client.key")
    14. if err != nil {
    15. log.Fatalf("tls.LoadX509KeyPair err: %v", err)
    16. }
    17. certPool := x509.NewCertPool()
    18. ca, err := ioutil.ReadFile("../../conf/ca.pem")
    19. if err != nil {
    20. log.Fatalf("ioutil.ReadFile err: %v", err)
    21. }
    22. if ok := certPool.AppendCertsFromPEM(ca); !ok {
    23. log.Fatalf("certPool.AppendCertsFromPEM err")
    24. }
    25. c := credentials.NewTLS(&tls.Config{
    26. Certificates: []tls.Certificate{cert},
    27. ServerName: "go-grpc-example",
    28. RootCAs: certPool,
    29. })
    30. conn, err := grpc.Dial(":"+PORT, grpc.WithTransportCredentials(c))
    31. if err != nil {
    32. log.Fatalf("grpc.Dial err: %v", err)
    33. }
    34. defer conn.Close()
    35. client := pb.NewSearchServiceClient(conn)
    36. resp, err := client.Search(context.Background(), &pb.SearchRequest{
    37. Request: "gRPC",
    38. })
    39. if err != nil {
    40. log.Fatalf("client.Search err: %v", err)
    41. }
    42. log.Printf("resp: %s", resp.GetResponse())
    43. }

    简单流程大致如下:

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

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

    验证

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

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

    这回是真的大功告成了!

    本系列示例代码