为gRPC添加上证书(上)

231 阅读11分钟

为什么要加证书?

  • HTTP在通信过程是明文传输的,还是人能看得懂的报文,不安全;gRPC虽然是二进制传输不易看懂,但是反序列化后还是明文的,所以放公网上通信是不太可能的。

  • 前面gRPC-gateway使用的是HTTP/1.1,如果想启用HTTP/2.0的特性,TLS加密是大概率需要的,至少在Go的HTTP/2.0推荐使用h2包是要求必须开启加密。

  • HTTP/2.0是TLS/SSL之上的协议,而gRPC通信是以HTTP/2为基础实现的

什么是证书?

简单的理解

例如HTTPS就是在HTTP的基础之上加入SSL/TLS协议,说点能懂的就是浏览器访问网站的时候会加载一个证书完成握手并随后加密通信信道,那证书是个什么鬼?

首先,加密的原理就是密钥把信息通过加密函数变成密文,解密就是把密文和明文调换过来

f(明文,密钥)=密文f\left( 明文,密钥\right)=密文
f(密文,密钥)=明文f(密文,密钥) = 明文

加密和解密用一个密钥,称为 对称加密

加密和解密用不同密钥,称为 非对称加密

对于SSL/TLS,分为握手和通信两部分,其中握手使用 非对称加密 ,通信使用 对称加密 ,我们不去深究其中的加密算法和握手时的密钥交换算法,理解密钥是用来干什么和怎么使用就好。

非对称加密

公钥私钥公钥 \approx 私钥
f(明文,公钥)=密文;f(密文,私钥)=明文f\left( 明文,公钥\right)=密文;f\left( 密文,私钥\right)=明文
f(明文,私钥)=密文;f(密文,公钥)=明文f\left( 明文,私钥\right)=密文;f\left( 密文,公钥\right)=明文

公钥和私钥是成对出现

公钥加密的信息只能用私钥解密,私钥加密的信息只能用公钥解密

密钥就是一组规定位数的字符串

如果把 密钥和国家地区、组织名、网站域名、邮箱等等必要信息打包成文件.CSR ,然后用这个 CSR权威机构(CA) 认证,CA中心再把一些必要信息附属到你打包的文件后面,然后再分发出一个文件,这个文件就是 证书

证书的一些比喻

  • CA:Certification Authority,证书机构,负责给各个网站颁发证书。

    专门给证书盖章的捞钱中心,申请盖章就得交钱,不得不说互联网兴起时的蓝海简直赚麻了,当然免费的也有,比如这个 Let's Encrypt

  • CSR:Certificate Signing Request,即证书签名请求。用户向CA申请证书的时候,需要提交的信息。一般会包含网站域名、用户邮箱、所在国家地区、组织名称......

    就是一群牛马辛苦准备好文件给捞钱中心盖章

  • 签发证书:即CA首先使用特定的摘要算法计算出CSR的摘要,再用自己的私钥给摘要进行加密后得到的签名,最后将以上所有信息打包为一个特定格式的证书。

    捞钱中心拿出专属的白玉私章,一支巨大形似鲨鱼的笔,盖章 + 签名

  • cert:Certificate,即证书。一个特定格式的文件,里面包含了CSR中部分信息、摘要生成算法、TLS拓展支持、CA给CSR的指纹(签名)。

    一份捞钱中心签过名盖过章的文件,逢人就可以说,那大鲨笔签过字盖过章的,就说屌不屌吧,就说你认不认

  • key:一般指openssl生成的密钥文件,其中包含了公钥和私钥。

    一群牛马的日常工作没啥好说的

  • 验证证书合法:指客户端首先从cert中读取该证书的签发CA(这里会涉及到证书链的问题,暂时可简单理解为,所有证书都是根证书机构签发),然后使用该CA的公钥去解密证书的签名(私钥加密,公钥解密),获得摘要信息A;然后使用证书中指定的摘要算法计算证书的摘要B;第三步判摘要A是否和摘要B相等。

    你得斟酌一下文件上章是不是白玉石盖的,字迹是不是用大鲨笔写出来的

  • tls单向认证:一般指客户端会验证服务端所提供的证书是否合法(访问https的网页就是用的这个,用的很广泛)。

  • tls双向认证:在单项认证基础上,服务端也会验证客户端的证书是否合法(跟金融打交道的话,你懂的)。

一个openssl制作证书的例子

1.冒充CA

在签发证书的时候要给CA提交证书签名请求,虽说有权威CA可以给你的证书免费签名,但是提交CSR里面有一项叫做 域名地址 ,这玩意要感官好点的地址可老要钱了,所以这里选择冒充CA自己给自己签发证书。

至于如何冒充CA的流程,参考 OpenSSL Certificate Authority — Jamie Nguyen

例子环境 ubuntu22.04,系统自带证书配置文件 /etc/ssl/openssl.cnf

在工程目录下新建如下结构:

cd configs
mkdir cert
cd cert
mkdir ca server usr
cd ca
mkdir certs crl newcerts private
touch root_ca.conf
touch index.txt
echo 1000 > serial
├── configs
│   └── cert
│       ├── ca                # 存放CA端证书
│       │   ├── certs
│       │   ├── crl
│       │   ├── index.txt
│       │   ├── newcerts
│       │   ├── private
│       │   ├── root_ca.conf  # 证书配置文件
│       │   └── serial
│       ├── server            # 存放服务端证书
│       └── usr               # 存放客户端证书

生成CA的公钥私钥对文件

cd ~/go-web/configs/cert/ca
openssl genrsa -out private/ca.key.pem 4096

测试一下加密解密

openssl的使用方法参考官方文档 OpenSSL commands - OpenSSL Documentation

# openssl版本
openssl version
OpenSSL 3.0.2 15 Mar 2022 (Library: OpenSSL 3.0.2 15 Mar 2022)

cd private
# 从密钥对中提取公钥
openssl rsa -pubout -in ca.key.pem -out ca.pub.key.pem
# 测试文件
echo "Hello, World" > text
# 公钥加密文件
openssl pkeyutl -encrypt -in text -inkey ca.pub.key.pem -pubin -out text.encrypt
# 私钥解密文件
openssl pkeyutl -decrypt -in text.encrypt -inkey ca.key.pem
# 输出原始数据,测试成功
Hello, World

生成CA证书

按照之前梳理的流程,生成密钥对之后,先打包一个CSR文件,然后再去CA签名,最终分发出证书,但是在这里,冒充CA的作用就是自己给自己签名,所以可以一步到位直接生成

openssl req -config root_ca.conf \
  -key private/ca.key.pem \
  -new -x509 -days 3650 \
  -out certs/ca.cert.pem

-----
Country Name (2 letter code) [CN]:
State or Province Name [Guangdong]:
Locality Name [Guangzhou]:
Organization Name [CA, Ltd]:
Organizational Unit Name []:
Common Name []:
Email Address []:
  • req 命令用来生成或者处理CSR

  • -key 参数用来指定密钥对文件存储位置

  • -new 参数用来生成新的CSR(从密钥对中提取公钥并加入其他信息打包CSR)

  • -x509 用来产生自签证书(从密钥对中提前私钥对CSR文件签名)

  • -config 指定证书配置文件,配置文件主要用到 [ req ]、[ req_distinguished_name ]、[ v3_ca ] 字段

之后会要求输入一堆其他信息,因为使用的是配置文件的形式生成,默认值已经手动指定,所以并没有输入其他信息

验证CA证书

openssl x509 -noout -text -in certs/ca.cert.pem

CA自签证书信息:

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            77:6c:fb:3c:f5:0f:f0:a5:ba:0b:81:3b:2c:a3:29:a3:4f:da:28:5c
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = CN, ST = Guangdong, L = Guangzhou, O = "CA, Ltd."
        Validity
            Not Before: Feb 24 09:44:27 2025 GMT
            Not After : Feb 22 09:44:27 2035 GMT
        Subject: C = CN, ST = Guangdong, L = Guangzhou, O = "CA, Ltd."
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (4096 bit)
    ......

CA的签名信息

    ......
X509v3 extensions:
            X509v3 Subject Key Identifier:
                AB:42:9A:E9:C3:73:B4:9F:A9:FD:3F:43:7E:43:07:7D:1D:60:9B:EC
            X509v3 Authority Key Identifier:
                AB:42:9A:E9:C3:73:B4:9F:A9:FD:3F:43:7E:43:07:7D:1D:60:9B:EC
            X509v3 Basic Constraints: critical
                CA:TRUE
            X509v3 Key Usage: critical
                Digital Signature, Certificate Sign, CRL Sign
    Signature Algorithm: sha256WithRSAEncryption

2.生成客户端证书

客户端的证书只能老老实实分两步去完成

1.公钥和其他信息打包生成CSR 2.CA用私钥给CSR签名,输出证书

Note 这里客户端是针对CA而言,也就是无论是用在服务器、代理、客户端......上的证书,对于CA来说都是需要被签名的客户端证书。

生成客户端密钥对

按照CA的前半流程,将配置文件复制一份并且生成密钥对

cd server
cp ../ca/root_ca.conf openssl.conf
openssl genrsa -out private/server.key.pem
.
├── ca
│   ├── certs
│   │   └── ca.cert.pem
│   ├── crl
│   ├── index.txt
│   ├── newcerts
│   ├── private
│   │   ├── ca.key.pem
│   │   └── ca.pub.key.pem
│   ├── root_ca.conf
│   └── serial
├── server
│   ├── certs                 # 新增
│   ├── csr                   # 存放csr
│   ├── openssl.conf          # copy from root_ca.conf, rename openssl.conf
│   └── private
│       └── server.key.pem    # 密钥对
└── usr

生成CSR

openssl req -config openssl.conf \
  -key private/server.key.pem \
  -new -out csr/server.csr.pem

Country Name (2 letter code) [CN]:
State or Province Name [Guangdong]:
Locality Name [Guangzhou]:
Organization Name [CA, Ltd.]:
Organizational Unit Name []:
Common Name []:grpc-gateway
Email Address []:

这里的 Common Name 输入 域名 或者 随意名字

├── server
│   ├── certs
│   ├── csr
│   │   └── server.csr.pem    # csr文件
│   ├── openssl.conf
│   └── private
│       └── server.key.pem

CA签名CSR并生成证书

CA签名需要用到CA的私钥和配置文件,所以切换到CA所在目录,在配置文件中定义有那么几个字段:

[ v3_intermediate_ca ] 用来签发中间CA

[ server_cert ] 用来签发服务端证书

[ usr_cert ] 用来签发客户端证书

目的时为了在签发时添加有明显特征的信息,在 -extensions 指定

openssl ca -config root_ca.conf \
  -extensions server_cert -days 2920 \
  -in ../server/csr/server.csr.pem \
  -out ../server/certs/server.cert.pem

确认信息并确认是否签发改CSR,签发成功后会更新 index.txt 数据库和 serial 序列号

Using configuration from root_ca.conf
Check that the request matches the signature
Signature ok
Certificate Details:
        Serial Number: 4096 (0x1000)
        Validity
            Not Before: Feb 24 09:51:37 2025 GMT
            Not After : Feb 22 09:51:37 2033 GMT
        Subject:
            countryName               = CN
            stateOrProvinceName       = Guangdong
            organizationName          = CA, Ltd.
            commonName                = grpc-gateway
        X509v3 extensions:
            X509v3 Basic Constraints:
                CA:FALSE
            Netscape Cert Type:
                SSL Server
            Netscape Comment:
                OpenSSL Generated Server Certificate
            X509v3 Subject Key Identifier:
                5A:46:B4:E1:90:D8:CB:8A:05:34:83:7E:7F:7D:21:FD:C6:72:AD:92
            X509v3 Authority Key Identifier:
                keyid:AB:42:9A:E9:C3:73:B4:9F:A9:FD:3F:43:7E:43:07:7D:1D:60:9B:EC
                DirName:/C=CN/ST=Guangdong/L=Guangzhou/O=CA, Ltd.
                serial:77:6C:FB:3C:F5:0F:F0:A5:BA:0B:81:3B:2C:A3:29:A3:4F:DA:28:5C
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage:
                TLS Web Server Authentication
Certificate is to be certified until Feb 22 09:51:37 2033 GMT (2920 days)
Sign the certificate? [y/n]:y

1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated

当确认签发CSR时, index.txt 记录所签发的CSR,serial 序列号也会更新,并生成证书

cat index.txt
V  330222095137Z  1000  unknown  /C=CN/ST=Guangdong/O=CA, Ltd./CN=grpc-gateway
cat serial
1001        # 最开始写入的是1000

验证客户端证书

openssl x509 -noout -text -in certs/server.cert.pem

用CA证书验证客户端证书

openssl verify -CAfile ca/certs/ca.cert.pem server/certs/server.cert.pem
server/certs/server.cert.pem: OK

完整的证书配置文件

参考 /etc/ssl/openssl.cnf 修修改改

[ ca ]
# `man ca`
default_ca = CA_default

[ CA_default ]
# Directory and file locations.
dir               = .
certs             = $dir/certs
crl_dir           = $dir/crl
new_certs_dir     = $dir/newcerts
database          = $dir/index.txt
serial            = $dir/serial

# The root key and root certificate.
private_key       = $dir/private/ca.key.pem
certificate       = $dir/certs/ca.cert.pem

# For certificate revocation lists.
crlnumber         = $dir/crlnumber
crl               = $dir/crl/ca.crl.pem
crl_extensions    = crl_ext
default_crl_days  = 30

default_md        = default
name_opt          = ca_default
cert_opt          = ca_default
default_days      = 365
preserve          = no
policy            = policy_match
x509_extensions      = usr_cert # The extensions to add to the cert

[ policy_match ]
# The root CA should only sign intermediate certificates that match.
# See the POLICY FORMAT section of `man ca`.
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ policy_anything ]
# Allow the intermediate CA to sign a more diverse range of certificates.
# See the POLICY FORMAT section of the `ca` man page.
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ req ]
# Options for the `req` tool (`man req`).
default_bits        = 2048
default_keyfile     = private/ca.key.pem
distinguished_name  = req_distinguished_name
string_mask         = utf8only
x509_extensions     = v3_ca # Extension to add when the -x509 option is used.

[ req_distinguished_name ]
# See <https://en.wikipedia.org/wiki/Certificate_signing_request>.
countryName                     = Country Name (2 letter code)
stateOrProvinceName             = State or Province Name
localityName                    = Locality Name
0.organizationName              = Organization Name
organizationalUnitName          = Organizational Unit Name
commonName                      = Common Name
emailAddress                    = Email Address

# Optionally, specify some defaults.
countryName_default             = CN
stateOrProvinceName_default     = Guangdong
localityName_default            = Guangzhou
0.organizationName_default      = CA, Ltd
organizationalUnitName_default  =
emailAddress_default            =

[ v3_ca ]
# Extensions for a typical CA (`man x509v3_config`).
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
basicConstraints = critical,CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ v3_intermediate_ca ]
# Extensions for a typical intermediate CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ server_cert ]
# Extensions for server certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth

[ usr_cert ]
# Extensions for client certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection

[ crl_ext ]
# Extension for CRLs (`man x509v3_config`).
authorityKeyIdentifier=keyid:always