RPC 編程 -六-:gRPC 中的證書認證
- 介紹
gRPC
建立在HTTP/2
協議之上,對TLS
提供了很好的支持。當不需要證書認證時, 可通過grpc.WithInsecure()
選項跳過了對服務器證書的驗證,沒有啓用證書的gRPC
服務和客戶端進行的是明文通信,信息面臨被任何第三方監聽的風險。爲了保證gRPC
通信不被第三方監聽、篡改或僞造,可以對服務器啓動TLS
加密特性。
- 概念簡述
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.1 生成 CA 證書
1. 新增ca.conf
[ req ]
default_bits = 4096
distinguished_name = req_distinguished_name
[ req_distinguished_name ]
countryName = GB
countryName_default = BeiJing
stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = JiangSu
localityName = Locality Name (eg, city)
localityName_default = NanJing
organizationName = Organization Name (eg, company)
organizationName_default = Step
commonName = liuqh.icu
commonName_max = 64
commonName_default = liuqh.icu
2. 生成 CA 私鑰
# 生成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 = CN
stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = JiangSu
localityName = Locality Name (eg, city)
localityName_default = NanJing
organizationName = Organization Name (eg, company)
organizationName_default = Step
commonName = CommonName (e.g. server FQDN or YOUR name)
commonName_max = 64
commonName_default = XXX(自定義,客戶端需要此字段做匹配)
[ req_ext ]
subjectAltName = @alt_names
[alt_names]
DNS.1 = liuqh.icu
IP = 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
- 目錄結構
- 代碼實現
5.1 服務端
package main
import (
"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 main
import (
"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: "liuqh.icu",
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>
# 故意寫錯客戶端中的 ServerName:test.com
$ 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"
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/1IvuGu9JvQ-oES-fEc5yJQ