go: grpc tls 应用一览

2023-01-31 14:59:05 浏览数 (2)

生成证书

在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:%vn", 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:%vn", 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也可以通过校验,与服务建立连接

0 人点赞