GOLANG:自建gRPC的TLS双向验证(OpenSSL实现)

Posted 大悦天

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GOLANG:自建gRPC的TLS双向验证(OpenSSL实现)相关的知识,希望对你有一定的参考价值。

gRPC

才不是因为键者周末无意中翻出了这个年初写了一半的稿子……

gRPCHTTP2

背景

gRPCTLSTokenTokenInterceptor自建鉴权
TLSTokenTLSConnectionTokenChanel
gRPCTLS
TLSHTTPSSHTTPOverTLSgRPCHTTP2TLSgRPCServerClientClientServer

写给新童鞋:更多的细节请参见拙作《》、《》。

HTTPSTLS

自建证书与OpenSSL

ServerClientCAServerCACAClient证书Client证书Server
CA
CACA私钥证书请求证书
OpenSSLOpenSSLTLS

安装方式略,相信在搜索引擎发达的今天有了关键字这都不是问题~

自建CA

不对称密钥CA
 
   
   
 
  1. openssl genrsa -out ca.key 4096

  2. - `genrsa` 生成 RSA 密钥对。

  3. - `-out` 令生成的密钥对保存到文件

  4. - `4096` RSA 模数位数,一般应大于1024。

ca.key私钥OpenSSLca.key私钥pqca.key公钥
RSA公钥私钥pqca.key公钥私钥
CAca.pubcrtcrt公钥CA数字证书CA

为什么会有这么个设计呢?这里先暂时按下不表……

ca.csrcsr基本信息公钥
 
   
   
 
  1. openssl req -new -key ca.key -out ca.csr

  2. - `req` 用于生成'证书请求'的 OpenSSL 命令。

  3. - `-new` 生成一个新的证书请求。该参数将令 OpenSSL 在证书请求生成过程中要求用户填写一些相应的字段。

  4. - `-key` 用于签名的CA私钥。

  5. - `-out` 将生成的请求保存到文件。

CSRca.csr
CommonNamelocalhostgRPCgRPCClientServer
ca.keyca.pubca.csr私钥ca.csr
私钥证书请求
 
   
   
 
  1. openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt

  2. - `x509` 操作符合x509标准的数字证书

  3. - `-req` 传入一个`证书请求`,对其签名并输出`证书`

  4. - `-int` 待签名的`证书请求`文件。

  5. - `-signkey` 用于签名的私钥。

  6. - `-out` 将生成的证书保存到文件

x509其实是数字证书的文件格式标准,一般我们用的数字证书都是符合x509标准的。

CACA的基础信息CA的公钥

另外我们要注意这三个步骤,其实签发证书基本就是这三个步骤的重复:

密钥对用户CAServerClient公钥证书请求私钥证书请求签名数字证书

当然,也有更丰富的参数可以控制生成的结果。例如,控制证书多久过期。

应用的关键在于:

  • 要信任谁,则表现为信任其证书,本质是信任证书的签发机构。

  • 要为谁做担保,则使用私钥为其请求签名,请求应包含被担保人的公钥。

CA
ServerClientCAServerCAca.crt
Client证书请求CAcli.crt

因此,我们可以推导过程如下:

 
   
   
 
  1. # 生成Client的`密钥对`

  2. openssl genrsa -out client.key 4096

  3. # 生成Client的`证书请求`

  4. openssl req -new -key client.key -out client.csr

  5. # 用CA的私钥为Client的`证书请求`签名,生成`数字证书`

  6. openssl x509 -req -in client.csr -signkey ca.key -out client.crt

其实还是那三个命令,只是参数发生了变化。

基于Golang的gRPC实现

那么,我们现在应该已经有了以下的文件:

 
   
   
 
  1. - ca.key

  2. - ca.crt

  3. - client.key

  4. - client.crt

gRPC-Server
 
   
   
 
  1. import (

  2.    "crypto/tls"

  3.    "crypto/x509"

  4.    "io/ioutil"

  5.    "google.golang.org/grpc"

  6.    "google.golang.org/grpc/credentials"

  7. )

  8. func NewServer()*grpc.Server{

  9.    // 准备一个信任的cert pool,pool中的存有当前server信任的所有的签发机构的证书。

  10.    certPool := x509.NewCertPool()

  11.    pemCACrtData, _ := ioutil.ReadFile("./ca.crt")

  12.    certPool.AppendCertsFromPEM(pemCACrtData)

  13.    // 生成gRPC的`TLS`配置

  14.    tlsConfig := tls.Config{

  15.        ClientCAs: certPool,

  16.        ClientAuth: tls.RequireAndVerifyClientCert, // 强制要求客户端提供证书进行身份认证,还有其他可选策略,有兴趣童鞋可以自己看看

  17.    }

  18.    creds := credentials.NewTLS(tlsConfig)

  19.    return grpc.NewServer(creds)

  20. }

对应客户端的核心代码如下:

 
   
   
 
  1. import (

  2.    "crypto/tls"

  3.    "crypto/x509"

  4.    "io/ioutil"

  5.    "google.golang.org/grpc"

  6.    "google.golang.org/grpc/credentials"

  7. )

  8. func NewClientConn()(*grpc.ClientConn,error){

  9.    // 读取CA签发给Client的证书

  10.    pemCliCrtData, _ := ioutil.ReadFile("./client.crt")

  11.    // 读取Client自己的私钥

  12.    pemCliKeyData, _ := ioutil.ReadFile("./client.key")

  13.    // 生成认证配置

  14.  cliCert,_ := tls.X509KeyPair(pemCliCrtData, pemCliKeyData)

  15.    // 生成tls配置

  16.    tlsConfig := &tls.Config{

  17.        Certificates: []tls.Certificate{cliCert},

  18.    }

  19.    creds := credentials.NewTLS(tlsConfig)  

  20.    // 生成ClientConn

  21.  return grpc.Dial(

  22.        "your-grpc-server",

  23.        grpc.WithTransportCredentials(creds),

  24.    )

  25. }

tls

关于私钥的密码

私钥PEM私钥crypto
 
   
   
 
  1. // 这段算法参考自`github.com/cloudflare/cfssl`的开源代码

  2. import (

  3.    "crypto/x509"

  4.    "encoding/pem"

  5.    "errors"

  6.    "io/ioutil"

  7. )

  8. // 简单说,pemPass就是用来加密的密钥

  9. func ReadPEMData(pemFile string, pemPass []byte) ([]byte, error) {

  10.    pemData, err := ioutil.ReadFile(pemFile)

  11.    if err != nil {

  12.        return pemData, err

  13.    }

  14.    // 先解析出pem block

  15.    pemBlock, _ := pem.Decode(pemData)

  16.    if pemBlock == nil {

  17.        return nil, errors.New("PEM Data Not Found")

  18.    }

  19.    // 判断这个block是否被加密过

  20.    if x509.IsEncryptedPEMBlock(pemBlock) {

  21.        // 解密并获得`ASN.1 DER`编码的数据

  22.        pemData, err = x509.DecryptPEMBlock(pemBlock, pemPass)

  23.        if err != nil {

  24.            return nil, err

  25.        }

  26.        // 重新生成一个没加密pem block

  27.        var newBlock pem.Block

  28.        newBlock.Type = pemBlock.Type

  29.        newBlock.Bytes = pemData

  30.        // 把未加密的pem block编码成tls可以直接解析的pemData

  31.        pemData = pem.EncodeToMemory(&newBlock)

  32.    }

  33.    return pemData, nil

  34. }

私钥证书

小结

数字证书篇

其实东西早用上了,就是键者拖延症末期,没救了……

数字证书

万丈高楼平地起,盘龙卧虎高山齐,各位小伙伴在下半年也要继续加油!