之前想通过 wireshark 抓包 gRPC 了解其基本通信的传输,整理的过程中看到有介绍 wireshark 解析 TLS 加密的 gRPC 通信的文章,想着可以一并学习,所以本篇先将 gRPC 通信使用自签名证书进行加密。
创建自签名证书
需要注意的是,本流程仅应用于测试,参数和安全性未经验证,不能用于生产环境。
想生成服务端和客户端证书,需要先生成一个证书颁发机构(Certificate Authority, CA),它将验证证书中所有者的身份。
1)生成 CA
$ openssl req -x509 -sha256 -newkey rsa:4096 -nodes -days 365 -keyout ca-key.pem -out ca-cert.pem -subj "/C=TR/ST=ASIA/L=ISTANBUL/O=DEV/OU=TUTORIAL/CN=serve.yasking.org/emailAddress=kissbug8720@gmail.com"
- 此处的
-sha256
需要注意,如果不指定默认是 SHA1,在 Golang gRPC 测试的时候有如下报错。
2022/11/23 21:33:57 could not greet: rpc error: code = Unavailable desc = connection error: desc = "transport: authentication handshake failed: x509: certificate signed by unknown authority (possibly because of \"x509: cannot verify signature: insecure algorithm SHA1-RSA (temporarily override with GODEBUG=x509sha1=1)\" while trying to verify candidate authority certificate \"serve.yasking.org\")" exit status 1
通过 openssl x509 -noout -text -in client-cert.pem
命令能查看证书信息,可以看到 Signature Algorithm: sha256WithRSAEncryption 字样。
2)生成服务端私钥和 CSR (Certificate Signing Request)
$ openssl req -sha256 -newkey rsa:4096 -nodes -keyout server-key.pem -out server-req.pem -subj "/C=TR/ST=ASIA/L=ISTANBUL/O=DEV/OU=BLOG/CN=serve.yasking.org/emailAddress=info@kissbug8720@gmail.com"
3)签署服务端证书
$ echo "subjectAltName=DNS:serve.yasking.org,IP:0.0.0.0" > server-ext.conf
$ openssl x509 -sha256 -req -in server-req.pem -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile server-ext.conf
4)生成客户端私钥和 CSR (Certificate Signing Request)
$ openssl req -sha256 -newkey rsa:4096 -nodes -keyout client-key.pem -out client-req.pem -subj "/C=TR/ST=EUROPE/L=ISTANBUL/O=DEV/OU=CLIENT/CN=serve.yasking.org/emailAddress=kissbug8720@gmail.com"
5)签署客户端证书
$ echo "subjectAltName=DNS:serve.yasking.org,IP:0.0.0.0" > client-ext.conf
$ openssl x509 -sha256 -req -in client-req.pem -days 60 -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -extfile client-ext.conf
在 Golang gRPC 中使用证书
基于 grpc 的 example/helloworld 示例改造
server.go
package main
import (
"context"
"flag"
"fmt"
"log"
"net"
"crypto/tls"
"crypto/x509"
"io/ioutil"
"google.golang.org/grpc"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
"google.golang.org/grpc/credentials"
)
var (
port = flag.Int("port", 50051, "The server port")
)
// server is used to implement helloworld.GreeterServer.
type server struct {
pb.UnimplementedGreeterServer
}
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
func main() {
flag.Parse()
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// read ca's cert, verify to client's certificate
caPem, err := ioutil.ReadFile("cert/ca-cert.pem")
if err != nil {
log.Fatal(err)
}
// create cert pool and append ca's cert
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(caPem) {
log.Fatal(err)
}
// read server cert & key
serverCert, err := tls.LoadX509KeyPair("cert/server-cert.pem", "cert/server-key.pem")
if err != nil {
log.Fatal(err)
}
// configuration of the certificate what we want to
conf := &tls.Config{
Certificates: []tls.Certificate{serverCert},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: certPool,
}
tlsCredentials := credentials.NewTLS(conf)
s := grpc.NewServer(grpc.Creds(tlsCredentials))
pb.RegisterGreeterServer(s, &server{})
log.Printf("server listening at %v", lis.Addr())
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
client.go
package main
import (
"context"
"flag"
"log"
"time"
"fmt"
"os"
"crypto/tls"
"crypto/x509"
"io/ioutil"
"google.golang.org/grpc/credentials"
"encoding/hex"
"google.golang.org/protobuf/proto"
"google.golang.org/grpc"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
)
const (
defaultName = "world"
)
var (
addr = flag.String("addr", "serve.yasking.org:50051", "the address to connect to")
name = flag.String("name", defaultName, "Name to greet")
)
func main() {
// read ca's cert
caCert, err := ioutil.ReadFile("cert/ca-cert.pem")
if err != nil {
log.Fatal(caCert)
}
// create cert pool and append ca's cert
certPool := x509.NewCertPool()
if ok := certPool.AppendCertsFromPEM(caCert); !ok {
log.Fatal(err)
}
//read client cert
clientCert, err := tls.LoadX509KeyPair("cert/client-cert.pem", "cert/client-key.pem")
if err != nil {
log.Fatal(err)
}
config := &tls.Config{
Certificates: []tls.Certificate{clientCert},
RootCAs: certPool,
}
tlsCredential := credentials.NewTLS(config)
flag.Parse()
// Set up a connection to the server.
conn, err := grpc.Dial(*addr,
grpc.WithTransportCredentials(tlsCredential),
grpc.WithBlock(),
)
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
// Contact the server and print out its response.
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
p := &pb.HelloRequest{Name: *name}
b, err := proto.Marshal(p)
if err != nil {
log.Fatal("proto marshal failed!")
}
fmt.Println(hex.EncodeToString(b))
r, err := c.SayHello(ctx, p)
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())
}
TLS 抓包截图
可以看到请求已显示为 TLS 加密,至于通信协议显示的是 TLSv1.3,详情内容显示 TLS 1.2,目前还没有精力深入研究。
至此,我们了解到如何生成自签名证书,并在 gRPC 通信中使用。