小猪教你实施CA证书双向认证

CA单向认证常用于https数据传输加密,避免在传输过程中被嗅探和篡改。而CA双向认证则更多的用于高安全场景的身份识别,它为每个client签发的证书内包含了各client的身份(例如银行证书的用户身份证号、车辆证书的车架号等),server在client请求时双向认证对方的证书有效性,同时server从有效client公钥中提取对方身份并与请求参数中的身份进行对比检查,以完成client身份的安全鉴别。下边简单介绍下CA证书双向认证原理和实施方法。

一、CA双向认证原理

1.根证书公/私钥对、服务端证书、客户端证书之间的关系

2.双向认证原理

所谓证书双向认证是指:

  • 服务端使用ca.crt校验客户端的client.crtclient.key
  • 客户端使用ca.crt校验服务端的server.crtserver.key

3.双向认证机制

1)客户端向服务端发送SSL协议版本号、加密算法种类、随机数等信息。
2)服务端给客户端返回SSL协议版本号、加密算法种类、随机数等信息,同时也返回服务器端的证书,即公钥证书
3)客户端使用服务端返回的信息验证服务器的合法性,包括:
证书是否过期
发行服务器证书的CA是否可靠
返回的公钥是否能正确解开返回证书中的数字签名
服务器证书上的域名是否和服务器的实际域名相匹配
验证通过后,将继续进行通信,否则,终止通信
4)服务端要求客户端发送客户端的证书,客户端会将自己的证书发送至服务端
5)验证客户端的证书,通过验证后,会获得客户端的公钥
6)客户端向服务端发送自己所能支持的对称加密方案,供服务器端进行选择
7)服务器端在客户端提供的加密方案中选择加密程度最高的加密方式
8)将加密方案通过使用之前获取到的公钥进行加密,返回给客户端
9)客户端收到服务端返回的加密方案密文后,使用自己的私钥进行解密,获取具体加密方式,而后,产生该加密方式的随机码,用作加密过程中的密钥,使用之前从服务端证书中获取到的公钥进行加密后,发送给服务端
10)服务端收到客户端发送的消息后,使用自己的私钥进行解密,获取对称加密的密钥,在接下来的会话中,服务器和客户端将会使用该密码进行对称加密,保证通信过程中信息的安全。

 

二、CA双向认证实施

*注:以下生成过程代码https://github.com/yanjingang/study/tree/master/ssl

1.生成根证书私钥

vim generate-PrivateCA.sh

# 0.创建一个新的 CA 根证书
#	sh generate-PrivateCA.sh
CA_ORG='/O=YanJingang.com/OU=YAN-CA/emailAddress=yanjingang@mail.com/countryName=CN/stateOrProvinceName=Beijing'
CA_DN="/CN=YAN DevRootCA${CA_ORG}"


# 创建证书目录
#	private 用于存放 CA 的私钥;
#	server 存放服务器证书文件;
#	client 存放客户端证书文件;
#	certs 子目录将用于存放 CA 签署过的数字证书(证书备份目录);
mkdir -p ./ca/private ./ca/server ./ca/client ./ca/certs

# 生成私钥 key 文件
openssl genrsa -out ./ca/private/ca.key 2048
echo "[DONE]	ca.key"

# 生成证书请求 csr 文件
openssl req -new -key ./ca/private/ca.key -out ./ca/private/ca.csr -subj "${CA_DN}"
echo "[DONE]	ca.csr"

# 生成凭证 crt 文件
openssl x509 -req -days 365 -in ./ca/private/ca.csr -signkey ./ca/private/ca.key -out ./ca/private/ca.crt
echo "[DONE]	ca.crt"

# 为我们的 key 设置起始序列号和创建 CA 键库
echo FACE > ./ca/serial 
# 可以是任意四个字符
touch ./ca/index.txt  
echo "[DONE]	index.txt"

# 为 "用户证书" 的移除创建一个证书撤销列表
openssl ca -gencrl -out ./ca/private/ca.crl -crldays 7 -config "./conf/openssl.conf" 
echo "[DONE]	ca.crl"

vim conf/openssl.conf

[ ca ] 
default_ca     = mars                   # The default ca section 

[ mars ] 
dir            = ./ca         # top dir  
database       = ./ca/index.txt          # index file.  
new_certs_dir  = ./ca/certs           # new certs dir 

certificate    = ./ca/private/ca.crt     # The CA cert  
serial         = ./ca/serial             # serial no file  
private_key    = ./ca/private/ca.key  # CA private key  
RANDFILE       = ./ca/private/.rand      # random number file 

default_days   = 365                  # how long to certify for  
default_crl_days= 30                  # how long before next CRL  
default_md     = sha1                 # message digest method to use  
unique_subject = no                   # Set to 'no' to allow creation of  
                                      # several ctificates with same subject. 
policy         = policy_any           # default policy 

[ policy_any ] 
countryName = match  
stateOrProvinceName = match 
organizationName = match  
organizationalUnitName = match  
localityName   = optional  
commonName     = supplied  
emailAddress   = optional  
#生成根证书私钥
sh generate-PrivateCA.sh

ll ca/private/
-rw-rw-r-- 1 work work  678 3月  23 17:19 ca.crl
-rw-rw-r-- 1 work work 1285 3月  23 17:19 ca.crt
-rw-rw-r-- 1 work work 1045 3月  23 17:19 ca.csr
-rw-rw-r-- 1 work work 1675 3月  23 17:19 ca.key

2.生成服务端证书

vim generate-ServerCA.sh

# 1.服务器证书的生成 
# 	sh generate-ServerCA.sh ca.yanjingang.com 
CA_ORG='/O=YanJingang.com/OU=YAN-CA/emailAddress=yanjingang@mail.com/countryName=CN/stateOrProvinceName=Beijing'
DOMAIN=$(hostname -f)  #服务端证书的域名
if [ -n "$1" ]; then
    DOMAIN="$1"
fi
SERVER_DN="/CN=${DOMAIN}${CA_ORG}"


# 创建一个 key
openssl genrsa -out ./ca/server/${DOMAIN}.key 2048
echo "[DONE]	${DOMAIN}.key"

# 为我们的 key 创建一个证书签名请求 csr 文件
openssl req -new -key ./ca/server/${DOMAIN}.key -out ./ca/server/${DOMAIN}.csr -subj "${SERVER_DN}"
echo "[DONE]	${DOMAIN}.csr"

# 使用我们私有的 CA key 为刚才的 key 签名
openssl ca -in ./ca/server/${DOMAIN}.csr \
    -cert ./ca/private/ca.crt \
    -keyfile ./ca/private/ca.key \
    -out ./ca/server/${DOMAIN}.crt \
    -config "./conf/openssl.conf" 
echo "[DONE]	${DOMAIN}.crt"
#生成服务端证书
sh generate-ServerCA.sh ca.yanjingang.com 

ll ca/server/
-rw-rw-r-- 1 work work 4059 3月  23 17:53 ca.yanjingang.com.crt
-rw-rw-r-- 1 work work 1050 3月  23 17:53 ca.yanjingang.com.csr
-rw-rw-r-- 1 work work 1679 3月  23 17:53 ca.yanjingang.com.key

 

3.生成客户端证书

vim generate-ClientCA.sh

# 2.客户端证书的生成 
#  sh generate-ClientCA.sh client1_sn1 
CA_ORG='/O=YanJingang.com/OU=YAN-CA/emailAddress=yanjingang@mail.com/countryName=CN/stateOrProvinceName=Beijing'
CLIENT_ID="client1"  #客户端证书的ID
if [ -n "$1" ]; then
    CLIENT_ID="$1"
fi
CLIENT_DN="${CA_ORG}/CN=YAN DevClientCA ${CLIENT_ID}"

# 为用户创建一个 key
#openssl genrsa -des3 -out ./ca/client/${CLIENT_ID}.key 2048
openssl genrsa -out ./ca/client/${CLIENT_ID}.key 2048
echo "[DONE]	${CLIENT_ID}.key"

# 为 key 创建一个证书签名请求 csr 文件
openssl req -new -key ./ca/client/${CLIENT_ID}.key -out ./ca/client/${CLIENT_ID}.csr  -subj "${CLIENT_DN}"
echo "[DONE]	${CLIENT_ID}.csr"

# 使用我们私有的 CA key 为刚才的 key 签名
openssl ca -in ./ca/client/${CLIENT_ID}.csr -cert ./ca/private/ca.crt -keyfile ./ca/private/ca.key -out ./ca/client/${CLIENT_ID}.crt -config "./conf/openssl.conf"
echo "[DONE]	${CLIENT_ID}.crt"

# 将证书转换为大多数浏览器都能识别的 PKCS12 文件
openssl pkcs12 -export -clcerts -in ./ca/client/${CLIENT_ID}.crt -inkey ./ca/client/${CLIENT_ID}.key -out ./ca/client/${CLIENT_ID}.p12  
echo "[DONE]	${CLIENT_ID}.p12"
#生成客户端证书
sh generate-ClientCA.sh client1_sn1

ll ca/client/
-rw-rw-r-- 1 work work 4073 3月  23 18:09 client1_sn1.crt
-rw-rw-r-- 1 work work 1058 3月  23 18:09 client1_sn1.csr
-rw-rw-r-- 1 work work 1675 3月  23 18:09 client1_sn1.key
-rw-rw-r-- 1 work work 2517 3月  23 18:09 client1_sn1.p12

 

4.配置nginx双向认证

server {
    listen              8443 ssl;  #监听https
    server_name         ca.yanjingang.com;

    # 开启https ssl
    ssl on;
    ssl_certificate      /home/work/project/study/ssl/ca/server/ca.yanjingang.com.crt;
    ssl_certificate_key  /home/work/project/study/ssl/ca/server/ca.yanjingang.com.key;
    ssl_session_timeout 20m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    # 开启ca双向认证
    ssl_verify_client on;
    ssl_client_certificate /home/work/project/study/ssl/ca/private/ca.crt;
    location / {
        fastcgi_pass    $php_upstream;
        fastcgi_index   index.php;
        include         fastcgi.conf;

        # client证书信息获取
        # 客户端证书内容
        fastcgi_param SSL_CLIENT_CERT       $ssl_client_cert;
        fastcgi_param SSL_CLIENT_RAW_CERT   $ssl_client_raw_cert;
        # 客户端证书验证结果(通过时这个变量值为SUCCESS)
        fastcgi_param SSL_CLIENT_VERIFY     $ssl_client_verify;
        # 客户端证书签发者信息
        fastcgi_param SSL_CLIENT_I_DN       $ssl_client_i_dn;
        # 客户端证书主题信息
        fastcgi_param SSL_CLIENT_S_DN       $ssl_client_s_dn;
        # 客户端证书序列号
        fastcgi_param SSL_CLIENT_SERIAL     $ssl_client_serial;
        # 客户端证书指纹
        fastcgi_param SSL_CLIENT_FPRINT     $ssl_client_fingerprint;
    }
    ...
}

 

5.测试双向认证

--无证书请求,拒绝访问
    curl https://ca.yanjingang.com
        curl: (60) SSL certificate problem: unable to get local issuer certificate
--携带根证书公钥请求,拒绝访问
    curl https://ca.yanjingang.com --cacert ./ca/private/ca.crt 
        400 No required SSL certificate was sent

--携带根证书公钥、客户端证书公钥/私钥请求,双向认证通过
    curl https://ca.yanjingang.com --cacert ./ca/private/ca.crt --cert ./ca/client/client1_sn1.crt --key ./ca/client/client1_sn1.key
        CA双向认证通过!
--或mac中双击client1.p12(p12中包含client公钥+私钥)添加到系统钥匙串,并设置“始终信任”,双向认证通过
    浏览器访问https://ca.yanjingang.com/
        CA双向认证通过!

6.客户端证书信息获取

--客户端证书生成自定义扩展信息
    客户端ID和+SN写入CN即可(generate-ClientCA.sh client1_sn1),不需要特殊的扩展位置;也方便服务端获取

--nginx配置
    location / {
        fastcgi_pass    $php_upstream;
        fastcgi_index   index.php;
        include         fastcgi.conf;

        # client证书信息获取
        # 客户端证书内容
        fastcgi_param SSL_CLIENT_CERT       $ssl_client_cert;
        fastcgi_param SSL_CLIENT_RAW_CERT   $ssl_client_raw_cert;
        # 客户端证书验证结果(通过时这个变量值为SUCCESS)
        fastcgi_param SSL_CLIENT_VERIFY     $ssl_client_verify;
        # 客户端证书签发者信息
        fastcgi_param SSL_CLIENT_I_DN       $ssl_client_i_dn;
        # 客户端证书主题信息
        fastcgi_param SSL_CLIENT_S_DN       $ssl_client_s_dn;
        # 客户端证书序列号
        fastcgi_param SSL_CLIENT_SERIAL     $ssl_client_serial;
        # 客户端证书指纹
        fastcgi_param SSL_CLIENT_FPRINT     $ssl_client_fingerprint;

    }

--服务端获取客户端证书的自定义信息
    <?php
    echo "CA Two Way Auth Success!\n";
    $keys = [
        'SSL_CLIENT_CERT',
        'SSL_CLIENT_RAW_CERT',
        'SSL_CLIENT_VERIFY',
        'SSL_CLIENT_I_DN',
        'SSL_CLIENT_S_DN',
        'SSL_CLIENT_SERIAL',
        'SSL_CLIENT_FPRINT',
    ];
    $client_id = "";
    foreach($keys as $k){
        echo "{$k}={$_SERVER[$k]}\n";
        // 提取client证书中的CN信息
        if($k == 'SSL_CLIENT_S_DN'){
            foreach(explode('/', $_SERVER['SSL_CLIENT_S_DN']) as $dn){
                $dn = explode('=', $dn);
                if($dn[0] == 'CN'){
                    $client_id = $dn[1];
                    break;
                }
            }
        }
    }
    echo "client_id = $client_id\n";
    ?>
# 测试
curl https://ca.yanjingang.com --cacert ./ca/private/ca.crt --cert ./ca/client/client1_sn1.crt --key ./ca/client/client1_sn1.key

CA Two Way Auth Success!
SSL_CLIENT_CERT = -----BEGIN CERTIFICATE-----
	MIIDgDCCAmgCAwD62DANBgkqhkiG9w0BAQUFADCBhTEWMBQGA1UEAwwNWUFOIERl
	dlJvb3RDQTEXMBUGA1UECgwOWWFuSmluZ2FuZy5jb20xDzANBgNVBAsMBllBTi1D
	...
	OvIgzFuDZ/MCFhf5lAJcT4u7sNjnFK1fgtxyVQkX9HuYcdAwBr4OJTb2pV1YMI7n
	x/mGTSmhwQkaeOFYFYJhrl+kj30iuRLBr1hUuMXMNTTdnzl8
	-----END CERTIFICATE-----
SSL_CLIENT_RAW_CERT = -----BEGIN CERTIFICATE-----
MIIDgDCCAmgCAwD62DANBgkqhkiG9w0BAQUFADCBhTEWMBQGA1UEAwwNWUFOIERl
dlJvb3RDQTEXMBUGA1UECgwOWWFuSmluZ2FuZy5jb20xDzANBgNVBAsMBllBTi1D
...
OvIgzFuDZ/MCFhf5lAJcT4u7sNjnFK1fgtxyVQkX9HuYcdAwBr4OJTb2pV1YMI7n
x/mGTSmhwQkaeOFYFYJhrl+kj30iuRLBr1hUuMXMNTTdnzl8
-----END CERTIFICATE-----

SSL_CLIENT_VERIFY = SUCCESS
SSL_CLIENT_I_DN = /CN=YAN DevRootCA/O=YanJingang.com/OU=YAN-CA/emailAddress=yanjingang@mail.com/C=CN/ST=Beijing
SSL_CLIENT_S_DN = /C=CN/ST=Beijing/O=YanJingang.com/OU=YAN-CA/CN=client1_sn1/emailAddress=yanjingang@mail.com
SSL_CLIENT_SERIAL = FAD8
SSL_CLIENT_FPRINT = 960f557871955abd03084db953ad401fb8b7033f
client_id = client1_sn1

 

yan 20.3.23 23:19

 

参考:

SSL模块(SSL)

HTTP client certificate var in nginx is blank?

Nginx SSL 双向认证,key 生成和配置

证书双向认证

客户端证书缺少TLS Web Client Authentication问题 tls: client’s certificate’s extended key usage doesn’t permit it to be used for client authentication

欢迎关注下方“非著名资深码农“公众号进行交流~

发表评论

邮箱地址不会被公开。