CA单向认证常用于https数据传输加密,避免在传输过程中被嗅探和篡改。而CA双向认证则更多的用于高安全场景的身份识别,它为每个client签发的证书内包含了各client的身份(例如银行证书的用户身份证号、车辆证书的车架号等),server在client请求时双向认证对方的证书有效性,同时server从有效client公钥中提取对方身份并与请求参数中的身份进行对比检查,以完成client身份的安全鉴别。下边简单介绍下CA证书双向认证原理和实施方法。
一、CA双向认证原理
1.根证书公/私钥对、服务端证书、客户端证书之间的关系
2.双向认证原理
所谓证书双向认证是指:
- 服务端使用
ca.crt
校验客户端的client.crt
和client.key
- 客户端使用
ca.crt
校验服务端的server.crt
和server.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
参考:
HTTP client certificate var in nginx is blank?