1. 介绍 gRPC
建立在HTTP/2
协议之上,对TLS
提供了很好的支持。当不需要证书认证时,可通过grpc.WithInsecure()
选项跳过了对服务器证书的验证,没有启用证书的gRPC
服务和客户端进行的是明文通信,信息面临被任何第三方监听的风险。为了保证gRPC
通信不被第三方监听、篡改或伪造,可以对服务器启动TLS
加密特性。
2. 概念简述 2.1 什么是CA CA
是Certificate Authority
的缩写,也叫“证书授权中心”。它是负责管理和签发证书的第三方机构,作用是检查证书持有者身份的合法性,并签发证书,以防证书被伪造或篡改。
CA
实际上是一个机构,负责“证件”印制核发。就像负责颁发身份证的公安局、负责发放行驶证、驾驶证的车管所。
2.2 什么是CA证书 CA
证书就是CA
颁发的证书。我们常听到的数字证书就是CA证书
,CA证书
包含信息有:证书拥有者的身份信息,CA机构的签名,公钥和私钥 ,
身份信息: 用于证明证书持有者的身份
CA机构的签名: 用于保证身份的真实性
公钥和私钥: 用于通信过程中加解密,从而保证通讯信息的安全性
2.3 什么是SAN
SAN(Subject Alternative Name)
是 SSL
标准 x509
中定义的一个扩展。使用了 SAN
字段的 SSL
证书,可以扩展此证书支持的域名,使得一个证书可以支持多个不同域名的解析。
2.4 为什么需要SAN
先看下不通过SAN
生成的证书,会报的错误信息。
rpc error: code = Unavailable desc = connection error: desc = "transport: authentication handshake failed: x509: certificate relies on legacy Common Name field, use SANs or temporarily enable Common Name matching with GODEBUG=x509ignoreCN=0"
出现上述错误,原因是因为 从go 1.15 版本开始废弃 CommonName ,因此推荐使用 SAN 证书。
3. 证书生成流程 3.1 生成CA证书 1. 新增ca.conf
[ req ] default_bits = 4096 distinguished_name = req_distinguished_name[ req_distinguished_name ] countryName = GBcountryName_default = BeiJingstateOrProvinceName = State or Province Name (full name)stateOrProvinceName_default = JiangSulocalityName = Locality Name (eg, city)localityName_default = NanJingorganizationName = Organization Name (eg, company)organizationName_default = StepcommonName = liuqh.icucommonName_max = 64 commonName_default = liuqh.icu
2. 生成CA私钥 $ openssl genrsa -out ca.key 4096
3. 生成CA证书 $ openssl req -new -x509 -days 365 -subj "/C=GB/L=Beijing/O=github/CN=liuqh.icu" \ -key ca.key -out ca.crt -config ca.conf
C=GB
: C
代表的是国家名称代码。
L=Beijing
: 代表地方名称,例如城市。
O=gobook
: 代表组织单位名称。
CN=liuqh.icu
: 代表关联的域名,
更多参数含义:https://www.digicert.com/kb/ssl-support/openssl-quick-reference-guide.htm#CreatingYourCSR
3.2 生成服务端证书 1. 新增server.conf
[ req ] default_bits = 2048 distinguished_name = req_distinguished_name[ req_distinguished_name ] countryName = Country Name (2 letter code)countryName_default = CNstateOrProvinceName = State or Province Name (full name)stateOrProvinceName_default = JiangSulocalityName = Locality Name (eg, city)localityName_default = NanJingorganizationName = Organization Name (eg, company)organizationName_default = StepcommonName = CommonName (e.g. server FQDN or YOUR name)commonName_max = 64 commonName_default = XXX(自定义,客户端需要此字段做匹配)[ req_ext ] subjectAltName = @alt_names[alt_names] DNS.1 = liuqh.icuIP = 127.0 .0.1
2.生成公私钥 $ openssl genrsa -out server.key 2048
3. 生成CSR
$ openssl req -new -subj "/C=GB/L=Beijing/O=github/CN=liuqh.icu" \ -key server.key -out server.csr -config server.conf
4. 基于CA签发证书 $ openssl x509 -req -sha256 -CA ca.crt -CAkey ca.key -CAcreateserial -days 365 \ -in server.csr -out server.crt -extensions req_ext -extfile server.conf
3.4 生成客户端证书 1. 生成公私钥 $ openssl genrsa -out client.key 2048
2. 生成CSR
$ openssl req -new -subj "/C=GB/L=Beijing/O=github/CN=liuqh.icu" \ -key client.key -out client.csr
3.基于CA签发证书 $ openssl x509 -req -sha256 -CA ca.crt -CAkey ca.key -CAcreateserial -days 365 \ -in client.csr -out client.crt
4. 目录结构
5.代码实现 5.1 服务端 package mainimport ( "52lu/go-rpc/server/tslservice" "crypto/tls" "crypto/x509" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "io/ioutil" "net" )func main () { pair, err := tls.LoadX509KeyPair("./pem/server.crt" , "./pem/server.key" ) if err != nil { fmt.Println("LoadX509KeyPair error" , err) return } certPool := x509.NewCertPool() ca, err := ioutil.ReadFile("./pem/ca.crt" ) if err != nil { fmt.Println("read ca pem error " , err) return } if ok := certPool.AppendCertsFromPEM(ca); !ok { fmt.Println("AppendCertsFromPEM error " ) return } cred := credentials.NewTLS(&tls.Config{ Certificates: []tls.Certificate{pair}, ClientAuth: tls.RequireAndVerifyClientCert, ClientCAs: certPool, }) grpcServer := grpc.NewServer(grpc.Creds(cred)) tslservice.RegisterTslServiceServer(grpcServer, new (tslservice.UnimplementedTslServiceServer)) listen, err := net.Listen("tcp" , ":1234" ) if err != nil { fmt.Println(err) return } fmt.Println("服务启动成功...." ) grpcServer.Serve(listen) }
5.2 客户端 package mainimport ( "52lu/go-rpc/server/tslservice" "context" "crypto/tls" "crypto/x509" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials" wrapperspb "google.golang.org/protobuf/types/known/wrapperspb" "io/ioutil" )func main () { pair, err := tls.LoadX509KeyPair("./pem/client.crt" , "./pem/client.key" ) if err != nil { fmt.Println("LoadX509KeyPair error " , err) return } certPool := x509.NewCertPool() ca, err := ioutil.ReadFile("./pem/ca.crt" ) if err != nil { fmt.Println("ReadFile ca.crt error " , err) return } if ok := certPool.AppendCertsFromPEM(ca); !ok { fmt.Println("certPool.AppendCertsFromPEM error " ) return } cred := credentials.NewTLS(&tls.Config{ Certificates: []tls.Certificate{pair}, ServerName: "test.com" , RootCAs: certPool, }) conn, err := grpc.Dial(":1234" , grpc.WithTransportCredentials(cred)) if err != nil { fmt.Println("dial error " , err) return } defer conn.Close() client := tslservice.NewTslServiceClient(conn) test, err := client.Test(context.TODO(), &wrapperspb.Int32Value{Value: 1 }) fmt.Println("res:" , test) fmt.Println("err:" , err) }
5.3 发起请求 $ go run client.go res: code:200 msg:"success" err: <nil> $ go run client.go res: <nil> err: rpc error: code = Unavailable desc = connection error: desc = "transport: authentication handshake failed: x509: certificate is valid for liuqh.icu, not test.com"