前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >go: grpc tls 应用一览

go: grpc tls 应用一览

作者头像
超级大猪
发布2023-01-31 14:59:05
1.3K0
发布2023-01-31 14:59:05
举报
文章被收录于专栏:大猪的笔记

生成证书

在go 1.15以上版本,必须使用SAN方式,否则会报"transport: authentication handshake failed: x509: certificate relies on legacy Common Name field, use SANs instead"

下面的示例在 go 1.19版本验证

rsa证书:

  1. # 生成ca证书 ca.key是私钥,服务管理员必须好好保管
  2. openssl genrsa -out ca.key 2048
  3. openssl req -days 3560 -new -x509 -key ca.key -subj "/C=CN/ST=GD/L=SZ/O=Hello/CN=Hello Root CA" -out ca.crt
  4. # 生成服务端证书并使用ca证书签发,注意`CN=hellosvc.com`,后续代码中需要用到。
  5. # server.key是私钥,必须好好保管
  6. openssl req -newkey rsa:2048 -nodes -keyout ./server/server.key -subj "/C=CN/ST=GD/L=SZ/O=Hello/CN=hellosvc.com" -out ./server/server.csr
  7. openssl x509 -days 3560 -req -extfile <(printf "subjectAltName=DNS:hellosvc.com") -in ./server/server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out ./server/server.crt
  8. # 生成客户端证书,这两个步骤由客户执行。执行完后,将client.csr提交给服务管理员,等待签发
  9. # CN=client.com 可以用做认证信息,以区分不同的客户
  10. openssl genrsa -out client/client.key 2048
  11. openssl req -new -key client/client.key -subj "/C=CN/ST=GD/L=SZ/O=HelloClient/CN=client.com/emailAddress=client@hello.com" -out client/client.csr
  12. # 使用ca签发,需要使用到ca.key,由服务管理员执行
  13. openssl x509 -req -sha256 -CA ca.crt -CAkey ca.key -CAcreateserial -days 365 -in client/client.csr -out client/client.crt

ecdsa证书:

  1. # 生成ca证书 ca.key是私钥,服务管理员必须好好保管
  2. openssl ecparam -name prime256v1 -genkey -noout -out ca.key
  3. openssl req -days 3560 -new -x509 -key ca.key -subj "/C=CN/ST=GD/L=SZ/O=Hello/CN=Hello Root CA" -out ca.crt
  4. # 生成服务端证书并使用ca证书签发,注意`CN=hellosvc.com`,后续代码中需要用到。
  5. # server.key是私钥,必须好好保管
  6. openssl ecparam -name prime256v1 -genkey -noout -out server/server.key
  7. openssl req -new -sha256 -key server/server.key -subj "/C=CN/ST=GD/L=SZ/O=Hello/CN=hellosvc.com" -out server/server.csr
  8. openssl x509 -days 3560 -req -extfile <(printf "subjectAltName=DNS:hellosvc.com") -in ./server/server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out ./server/server.crt
  9. # 生成客户端证书,这两个步骤由客户执行。执行完后,将client.csr提交给服务管理员,等待签发
  10. # CN=client.com 可以用做认证信息,以区分不同的客户
  11. openssl ecparam -name prime256v1 -genkey -noout -out client/client.key
  12. openssl req -new -sha256 -key client/client.key -subj "/C=CN/ST=GD/L=SZ/O=HelloClient/CN=client.com/emailAddress=client@hello.com" -out client/client.csr
  13. # 使用ca签发,需要使用到ca.key,由服务管理员执行
  14. openssl x509 -req -sha256 -CA ca.crt -CAkey ca.key -CAcreateserial -days 365 -in client/client.csr -out client/client.crt

双向认证

服务端代码

  1. func tlsGrpcSvr() {
  2. // 加载服务端私钥和证书
  3. cert, err := tls.LoadX509KeyPair("./server/server.crt", "./server/server.key")
  4. if err != nil {
  5. panic(err)
  6. }
  7. // 生成证书池,将根证书加入证书池
  8. certPool := x509.NewCertPool()
  9. rootBuf, err := ioutil.ReadFile("ca.crt")
  10. if err != nil {
  11. panic(err)
  12. }
  13. if !certPool.AppendCertsFromPEM(rootBuf) {
  14. panic("Fail to append ca")
  15. }
  16. // 初始化TLSConfig
  17. // ClientAuth有5种类型,如果要进行双向认证必须是RequireAndVerifyClientCert
  18. tlsConf := &tls.Config{
  19. ClientAuth: tls.RequireAndVerifyClientCert,
  20. Certificates: []tls.Certificate{cert},
  21. ClientCAs: certPool,
  22. }
  23. var keepAliveArgs = keepalive.ServerParameters{
  24. Time: 10 * time.Second,
  25. Timeout: 20 * time.Second,
  26. MaxConnectionAge: 30 * time.Second,
  27. }
  28. lis, err := net.Listen("tcp", fmt.Sprintf(":%d", 6688))
  29. if err != nil {
  30. log.Fatalf("failed to listen: %v", err)
  31. }
  32. s := grpc.NewServer(
  33. grpc.KeepaliveParams(keepAliveArgs),
  34. grpc.MaxSendMsgSize(1024*1024*4),
  35. grpc.Creds(credentials.NewTLS(tlsConf)),
  36. )
  37. // 注册服务
  38. pb.RegisterHelloSvcServer(s, &services.HelloServer{})
  39. reflection.Register(s)
  40. fmt.Printf("run:%v\n", 6688)
  41. if err := s.Serve(lis); err != nil {
  42. log.Fatalf("failed to serve: %v", err)
  43. }
  44. }

客户端代码

  1. func TestSendHelloTls(t *testing.T) {
  2. // 加载客户端私钥和证书
  3. cert, err := tls.LoadX509KeyPair("/root/workspace/personal/grpc_example/client/client.crt",
  4. "/root/workspace/personal/grpc_example/client/client.key")
  5. if err != nil {
  6. panic(err)
  7. }
  8. // 将根证书加入证书池
  9. certPool := x509.NewCertPool()
  10. rootBuf, err := ioutil.ReadFile("/root/workspace/personal/grpc_example/ca.crt")
  11. if err != nil {
  12. panic(err)
  13. }
  14. if !certPool.AppendCertsFromPEM(rootBuf) {
  15. panic("Fail to append ca")
  16. }
  17. // 注意ServerName需要与服务器证书内的CN一致
  18. creds := credentials.NewTLS(&tls.Config{
  19. ServerName: "hellosvc.com",
  20. Certificates: []tls.Certificate{cert},
  21. RootCAs: certPool,
  22. })
  23. conn, err := grpc.Dial("127.0.0.1:6688", grpc.WithTransportCredentials(creds))
  24. if err != nil {
  25. panic(err)
  26. }
  27. defer conn.Close()
  28. client := hello.NewHelloSvcClient(conn)
  29. // ...
  30. }

此时,客户端必须要用ca签发的证书才能和服务端建立连接

添加额外检验 - 只允许特定的CN(common name)

在创建客户端证书的时候,指定了CN=client.com

  1. # CN=client.com
  2. openssl req -new -key client/client.key -subj "/C=CN/ST=GD/L=SZ/O=HelloClient/CN=client.com/emailAddress=client@hello.com" -out client/client.csr

grpc允许在tls流程中对证书进行额外的检验,如下所示

定义VerifyPeerCertificate,当CN不在白名单中,则拒绝连接。

  1. tlsConf := &tls.Config{
  2. ClientAuth: tls.RequireAndVerifyClientCert,
  3. Certificates: []tls.Certificate{cert},
  4. ClientCAs: certPool,
  5. VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
  6. for _, chain := range verifiedChains {
  7. for _, v := range chain {
  8. if !slices.Contains([]string{"client.com", "Hello Root CA"}, v.Subject.CommonName) {
  9. return fmt.Errorf("common name is invalid")
  10. }
  11. }
  12. }
  13. return nil
  14. },
  15. }

注意,chains里会包含CA的信息,所以把CA的common name Hello Root CA 也包含进去。

有了这个验证,即使证书是CA签发的,只要common name != “client.com”,连接无法建立,此时会报错如下:

  1. hellosvc_test.go:: rpc error: code = Unavailable desc = connection error: desc = "error reading server preface: remote error: tls: bad certificate"

仅服务端tls

服务端代码

  1. lis, err := net.Listen("tcp", fmt.Sprintf(":%d", ))
  2. if err != nil {
  3. log.Fatalf("failed to listen: %v", err)
  4. }
  5. // 指定使用服务端证书创建一个 TLS credentials。
  6. creds, err := credentials.NewServerTLSFromFile("./server/server.crt", "./server/server.key")
  7. if err != nil {
  8. log.Fatalf("failed to create credentials: %v", err)
  9. }
  10. // 指定使用 TLS credentials。
  11. s := grpc.NewServer(grpc.Creds(creds))
  12. pb.RegisterHelloSvcServer(s, &services.HelloServer{})
  13. reflection.Register(s)
  14. fmt.Printf("run:%v\n", 6688)
  15. if err := s.Serve(lis); err != nil {
  16. log.Fatalf("failed to serve: %v", err)
  17. }

客户端代码

  1. root := "/root/workspace/personal/grpc_example/"
  2. // 客户端通过ca证书来验证服务的提供的证书
  3. creds, err := credentials.NewClientTLSFromFile(root+"ca.crt", "hellosvc.com")
  4. if err != nil {
  5. log.Fatalf("failed to load credentials: %v", err)
  6. }
  7. // 建立连接时指定使用 TLS
  8. conn, err := grpc.Dial("127.0.0.1:6688", grpc.WithTransportCredentials(creds))
  9. if err != nil {
  10. log.Fatalf("did not connect: %v", err)
  11. }
  12. defer conn.Close()
  13. client := hello.NewHelloSvcClient(conn)

错误的签发证书无法建立连接

尝试创建另一个ca2根证书,并签发client2证书,这个证书不可与上述的服务建立连接

  1. openssl genrsa -out ca2.key
  2. openssl req -new -x509 -key ca2.key -subj "/C=CN/ST=GD/L=SZ/O=Test, Inc./CN=Test Root CA" -out ca2.crt
  3. openssl genrsa -out client/client2.key 2048
  4. openssl req -new -key client/client2.key -subj "/C=CN/ST=GD/L=SZ/O=HelloClient/CN=client.com/emailAddress=client@hello.com" -out client/client2.csr
  5. openssl x509 -req -sha256 -CA ca2.crt -CAkey ca2.key -CAcreateserial -days 365 -in client/client2.csr -out client/client2.crt

在客户端代码中使用 client2.key 尝试连接服务。因为证书不合法,会直接报错:

  1. hellosvc_test.go:: rpc error: code = Unavailable desc = connection error: desc = "transport: authentication handshake failed: x509: certificate signed by unknown authority (possibly because of \"crypto/rsa: verification error\" while trying to verify candidate authority certificate \"Test Root CA\")"

使用代码签发证书

在上面的步骤,当客户提交了 csr 文件后,可以使用命令对csr进行签发,并生成crt证书

  1. openssl x509 -req -sha256 -CA ca.crt -CAkey ca.key -CAcreateserial -days -in client/client.csr -out client/client.crt

有时,命令行的方式并不方便,需要使用代码方式签发证书(如建立一个签发的api),示例如下:

参考:https://stackoverflow.com/questions/42643048/signing-certificate-request-with-certificate-authority

  1. func TestSigningCsr(t *testing.T) {
  2. root := "/root/workspace/personal/grpc_example/"
  3. // load CA key pair
  4. // public key
  5. caPublicKeyFile, err := ioutil.ReadFile(root + "/ca.crt")
  6. if err != nil {
  7. panic(err)
  8. }
  9. pemBlock, _ := pem.Decode(caPublicKeyFile)
  10. if pemBlock == nil {
  11. panic("pem.Decode failed")
  12. }
  13. caCRT, err := x509.ParseCertificate(pemBlock.Bytes)
  14. if err != nil {
  15. panic(err)
  16. }
  17. // private key
  18. caPrivateKeyFile, err := ioutil.ReadFile(root + "ca.key")
  19. if err != nil {
  20. panic(err)
  21. }
  22. pemBlock, _ = pem.Decode(caPrivateKeyFile)
  23. if pemBlock == nil {
  24. panic("pem.Decode failed")
  25. }
  26. // der, err := x509.DecryptPEMBlock(pemBlock, []byte("ca private key password"))
  27. // if err != nil {
  28. // panic(err)
  29. // }
  30. // caPrivateKey, err := x509.ParsePKCS1PrivateKey(der)
  31. // 解析rsa证书
  32. // caPrivateKey, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes)
  33. // 解析ec证书
  34. caPrivateKey, err := x509.ParseECPrivateKey(pemBlock.Bytes)
  35. if err != nil {
  36. panic(err)
  37. }
  38. // load client certificate request
  39. clientCSRFile, err := ioutil.ReadFile(root + "client/client2.csr")
  40. if err != nil {
  41. panic(err)
  42. }
  43. pemBlock, _ = pem.Decode(clientCSRFile)
  44. if pemBlock == nil {
  45. panic("pem.Decode failed")
  46. }
  47. clientCSR, err := x509.ParseCertificateRequest(pemBlock.Bytes)
  48. if err != nil {
  49. panic(err)
  50. }
  51. if err = clientCSR.CheckSignature(); err != nil {
  52. panic(err)
  53. }
  54. // create client certificate template
  55. clientCRTTemplate := x509.Certificate{
  56. Signature: clientCSR.Signature,
  57. SignatureAlgorithm: clientCSR.SignatureAlgorithm,
  58. PublicKeyAlgorithm: clientCSR.PublicKeyAlgorithm,
  59. PublicKey: clientCSR.PublicKey,
  60. SerialNumber: big.NewInt(2),
  61. Issuer: caCRT.Subject,
  62. Subject: clientCSR.Subject,
  63. NotBefore: time.Now(),
  64. NotAfter: time.Now().Add(24 * time.Hour * 365),
  65. KeyUsage: x509.KeyUsageDigitalSignature,
  66. ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
  67. }
  68. // create client certificate from template and CA public key
  69. clientCRTRaw, err := x509.CreateCertificate(rand.Reader, &clientCRTTemplate, caCRT, clientCSR.PublicKey, caPrivateKey)
  70. if err != nil {
  71. panic(err)
  72. }
  73. // save the certificate
  74. clientCRTFile, err := os.Create(root + "client/client2.crt")
  75. if err != nil {
  76. panic(err)
  77. }
  78. pem.Encode(clientCRTFile, &pem.Block{Type: "CERTIFICATE", Bytes: clientCRTRaw})
  79. clientCRTFile.Close()
  80. }

生成的client2.crt也可以通过校验,与服务建立连接

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-12-28 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 生成证书
  • 双向认证
    • 服务端代码
      • 客户端代码
        • 添加额外检验 - 只允许特定的CN(common name)
        • 仅服务端tls
          • 服务端代码
            • 客户端代码
            • 错误的签发证书无法建立连接
            • 使用代码签发证书
            相关产品与服务
            SSL 证书
            腾讯云 SSL 证书(SSL Certificates)为您提供 SSL 证书的申请、管理、部署等服务,为您提供一站式 HTTPS 解决方案。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档