Posted 大悦天
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GOLANG:自建gRPC的TLS双向验证(OpenSSL实现)相关的知识,希望对你有一定的参考价值。
gRPC
才不是因为键者周末无意中翻出了这个年初写了一半的稿子……
gRPCHTTP2
背景
gRPCTLSTokenTokenInterceptor自建鉴权
TLSTokenTLSConnectionTokenChanel
gRPCTLS
TLSHTTPSSHTTPOverTLSgRPCHTTP2TLSgRPCServerClientClientServer
写给新童鞋:更多的细节请参见拙作《》、《》。
HTTPSTLS
自建证书与OpenSSL
ServerClientCAServerCACAClient证书Client证书Server
CA
CACA私钥证书请求证书
OpenSSLOpenSSLTLS
安装方式略,相信在搜索引擎发达的今天有了关键字这都不是问题~
自建CA
不对称密钥CA
openssl genrsa -out ca.key 4096
- `genrsa` 生成 RSA 密钥对。
- `-out` 令生成的密钥对保存到文件
- `4096` RSA 模数位数,一般应大于1024。
ca.key私钥OpenSSLca.key私钥pqca.key公钥
RSA公钥私钥pqca.key公钥私钥
CAca.pubcrtcrt公钥CA数字证书CA
为什么会有这么个设计呢?这里先暂时按下不表……
ca.csrcsr基本信息公钥
openssl req -new -key ca.key -out ca.csr
- `req` 用于生成'证书请求'的 OpenSSL 命令。
- `-new` 生成一个新的证书请求。该参数将令 OpenSSL 在证书请求生成过程中要求用户填写一些相应的字段。
- `-key` 用于签名的CA私钥。
- `-out` 将生成的请求保存到文件。
CSRca.csr
CommonNamelocalhostgRPCgRPCClientServer
ca.keyca.pubca.csr私钥ca.csr
私钥证书请求
openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt
- `x509` 操作符合x509标准的数字证书
- `-req` 传入一个`证书请求`,对其签名并输出`证书`
- `-int` 待签名的`证书请求`文件。
- `-signkey` 用于签名的私钥。
- `-out` 将生成的证书保存到文件
x509其实是数字证书的文件格式标准,一般我们用的数字证书都是符合x509标准的。
CACA的基础信息CA的公钥
另外我们要注意这三个步骤,其实签发证书基本就是这三个步骤的重复:
密钥对用户CAServerClient公钥证书请求私钥证书请求签名数字证书
当然,也有更丰富的参数可以控制生成的结果。例如,控制证书多久过期。
应用的关键在于:
要信任谁,则表现为信任其证书,本质是信任证书的签发机构。
要为谁做担保,则使用私钥为其请求签名,请求应包含被担保人的公钥。
CA
ServerClientCAServerCAca.crt
Client证书请求CAcli.crt
因此,我们可以推导过程如下:
# 生成Client的`密钥对`
openssl genrsa -out client.key 4096
# 生成Client的`证书请求`
openssl req -new -key client.key -out client.csr
# 用CA的私钥为Client的`证书请求`签名,生成`数字证书`
openssl x509 -req -in client.csr -signkey ca.key -out client.crt
其实还是那三个命令,只是参数发生了变化。
基于Golang的gRPC实现
那么,我们现在应该已经有了以下的文件:
- ca.key
- ca.crt
- client.key
- client.crt
gRPC-Server
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
func NewServer()*grpc.Server{
// 准备一个信任的cert pool,pool中的存有当前server信任的所有的签发机构的证书。
certPool := x509.NewCertPool()
pemCACrtData, _ := ioutil.ReadFile("./ca.crt")
certPool.AppendCertsFromPEM(pemCACrtData)
// 生成gRPC的`TLS`配置
tlsConfig := tls.Config{
ClientCAs: certPool,
ClientAuth: tls.RequireAndVerifyClientCert, // 强制要求客户端提供证书进行身份认证,还有其他可选策略,有兴趣童鞋可以自己看看
}
creds := credentials.NewTLS(tlsConfig)
return grpc.NewServer(creds)
}
对应客户端的核心代码如下:
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
func NewClientConn()(*grpc.ClientConn,error){
// 读取CA签发给Client的证书
pemCliCrtData, _ := ioutil.ReadFile("./client.crt")
// 读取Client自己的私钥
pemCliKeyData, _ := ioutil.ReadFile("./client.key")
// 生成认证配置
cliCert,_ := tls.X509KeyPair(pemCliCrtData, pemCliKeyData)
// 生成tls配置
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cliCert},
}
creds := credentials.NewTLS(tlsConfig)
// 生成ClientConn
return grpc.Dial(
"your-grpc-server",
grpc.WithTransportCredentials(creds),
)
}
tls
关于私钥的密码
私钥PEM私钥crypto
// 这段算法参考自`github.com/cloudflare/cfssl`的开源代码
import (
"crypto/x509"
"encoding/pem"
"errors"
"io/ioutil"
)
// 简单说,pemPass就是用来加密的密钥
func ReadPEMData(pemFile string, pemPass []byte) ([]byte, error) {
pemData, err := ioutil.ReadFile(pemFile)
if err != nil {
return pemData, err
}
// 先解析出pem block
pemBlock, _ := pem.Decode(pemData)
if pemBlock == nil {
return nil, errors.New("PEM Data Not Found")
}
// 判断这个block是否被加密过
if x509.IsEncryptedPEMBlock(pemBlock) {
// 解密并获得`ASN.1 DER`编码的数据
pemData, err = x509.DecryptPEMBlock(pemBlock, pemPass)
if err != nil {
return nil, err
}
// 重新生成一个没加密pem block
var newBlock pem.Block
newBlock.Type = pemBlock.Type
newBlock.Bytes = pemData
// 把未加密的pem block编码成tls可以直接解析的pemData
pemData = pem.EncodeToMemory(&newBlock)
}
return pemData, nil
}
私钥证书
小结
数字证书篇
其实东西早用上了,就是键者拖延症末期,没救了……
数字证书
万丈高楼平地起,盘龙卧虎高山齐,各位小伙伴在下半年也要继续加油!