使用golang的grpc库作为例子

grpc使用TLS建立安全连接需要有SAN证书, go 1.15 版本开始废弃 CommonName。

什么是SAN证书

SAN(Subject Alternative Name) 是 SSL 标准 x509 中定义的一个扩展。使用了 SAN 字段的 SSL 证书,可以扩展此证书支持的域名,使得一个证书可以支持多个不同域名的解析。

制作SAN证书

一条指令生成ca证书

$ openssl req \
        -x509 \
        -nodes \
        -newkey rsa:2048 \
        -keyout keys/ca.key \
        -out keys/ca.crt \
        -days 3650 \
        -subj "/C=CN/ST=ZheJiang/L=ZheJiang/O=Global/CN=servername Security/OU=IT"

-sub 后面参数的意义; C => Country; ST => State; L => City; O => Organization; OU => Organization Unit; CN => Common Name (证书所请求的域名); emailAddress => main administrative point of contact for the certificate ;

生成私钥 & 根据私钥server.key生成证书请求文件server.csr:

$ openssl genpkey -algorithm RSA -out server.key
$ openssl req -new -nodes -key server.key -out server.csr -days 3650 \
  -subj "/C=CN/OU=IT/O=Global/CN=servername/L=ZheJiang" \
  -config openssl.cnf -extensions v3_req

验证证书CSR的扩展属性(可选)

$ openssl req -noout -text -in server.csr

生成san证书

$ openssl x509 -req -days 365 -in server.csr -out server.pem \
  -CA ca.crt -CAkey ca.key -CAcreateserial \
  -extfile openssl.cnf -extensions v3_req
  • server.csr是前面步骤生成的证书请求文件。
  • ca.crt & ca.key 是CA证书文件和key,用来对server.csr进行签名认证。

查看SAN信息在证书内容(可选)

$ openssl x509 -noout -text -in server.pem

现在证书已经生成完毕, server.pem 和 server.key正式我们需要的证书和密钥

Grpc 使用SAN证书建立安全通道

proto文件

proto
//Hello.proto
syntax = "proto3";
option go_package = "./pb;pb";

package hello;

message HelloWorld {
  string msg = 1;
}

service Hello {
  rpc SayHello(HelloWorld) returns(HelloWorld) {}
}

生成*.pb.go文件

$ protoc -I. --go_out=.  --go-grpc_out=. ./*proto

server端

golang
package main

import (
	"context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"log"
	"net"
	"pro2d/protos/pb"
)

type Server struct {
	pb.UnimplementedHelloServer
}

func (s *Server) SayHello(ctx context.Context, in *pb.HelloWorld) (*pb.HelloWorld, error)  {
	return in, nil
}

func main() {
	// 监听本地端口
	listener, err := net.Listen("tcp", ":8948")
	if err != nil {
		log.Fatalf("net.Listen err: %v", err)
	}
	var opts []grpc.ServerOption
	// 从输入证书文件和密钥文件为服务端构造TLS凭证
	creds, err := credentials.NewServerTLSFromFile("keys/server.pem", "keys/server.key")
	if err != nil {
		log.Fatalf("Failed to generate credentials %v", err)
	}
	opts = append(opts, grpc.Creds(creds))
	// 新建gRPC服务器实例,并开启TLS认证
	grpcServer := grpc.NewServer(opts...)

	// 在gRPC服务器注册我们的服务
	pb.RegisterHelloServer(grpcServer, &Server{})
	log.Println(" net.Listing whth TLS")
	//用服务器 Serve() 方法以及我们的端口信息区实现阻塞等待,直到进程被杀死或者 Stop() 被调用
	err = grpcServer.Serve(listener)
	if err != nil {
		log.Fatalf("grpcServer.Serve err: %v", err)
	}
}

client端

golang
package main

import (
	"context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"log"
	"pro2d/protos/pb"
)

func main() {
	var opts []grpc.DialOption
	creds, err := credentials.NewClientTLSFromFile("keys/server.pem", "servername")
	if err != nil {
		log.Fatal(err)
		return
	}
	opts = append(opts, grpc.WithTransportCredentials(creds))
	conn, err := grpc.Dial("localhost:8948", opts...)

	helloClient := pb.NewHelloClient(conn)
	rsp, err := helloClient.SayHello(context.TODO(), &pb.HelloWorld{Msg: "hello world"})
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("sayhello rsp: %v", rsp)
}

注意 creds, err := credentials.NewClientTLSFromFile("keys/server.pem", "servername")这里的servernaem参数要与证书申请的一致

--完--