场景介绍

xxx.com 为正常域名 配置 正常SSL
api.xxx.com 配置双向验证(自签名的CA基本没费用)

nginx 配置

在宝塔站点配置文件增加如下内容

server
{
	listen 443 ssl http2;
    server_nameapi.xxx.com;
    index index.php index.html index.htm default.php default.htm default.html;
    root /www/wwwroot//www/api;
    
    #SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
    #error_page 404/404.html;
    #HTTP_TO_HTTPS_START
    if ($server_port !~ 443){
        rewrite ^(/.*)$ https://$host$1 permanent;
    }
    #HTTP_TO_HTTPS_END
    ssl_certificate    /www/wwwroot/xxx/www/ca_cert/xxx.cn_chain.crt;
    ssl_certificate_key    /www/wwwroot/xxx/www/ca_cert/xxx.cn_key.key;
    ssl_client_certificate /www/wwwroot/xxx/www/ca_cert/ca.crt;
    ssl_verify_client on;
    ssl_protocols TLSv1.1 TLSv1.2;
    ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
   
    add_header Strict-Transport-Security "max-age=31536000";

    #SSL-END
    
    #ERROR-PAGE-START  错误页配置,可以注释、删除或修改
    #error_page 400 /404.html;
    #error_page 502 /502.html;
    #客户端证书验证过程中发生错误
    error_page 495 /495.html;
    #客户没有出示所需的证书
    error_page 496 /496.html;
    #常规请求已发送到 HTTPS 端口
    error_page 497 /497.html;
    #ERROR-PAGE-END
    
    #PHP-INFO-START  PHP引用配置,可以注释或修改
    include enable-php-73.conf;
    #PHP-INFO-END
    
    
    #禁止访问的文件或目录
    location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.project|LICENSE|README.md)
    {
        return 404;
    }
    
    location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
    {
        expires      30d;
        error_log /dev/null;
        access_log /dev/null;
    }
    
    location ~ .*\.(js|css)?$
    {
        expires      12h;
        error_log /dev/null;
        access_log /dev/null; 
    }
}

错误页(自定义)

<html>
<head><title>497 The SSL certificate error</title></head>
<body bgcolor="white">
<center><h1>497 </h1></center>
<center>常规请求已发送到 HTTPS 端口</center>
<hr><center>OenSSL</center>
</body>
</html>
<html>
<head><title>496 The SSL certificate error</title></head>
<body bgcolor="white">
<center><h1>496 </h1></center>
<center>客户没有出示所需的证书</center>
<hr><center>OenSSL</center>
</body>
</html>
<html>
<head><title>496 The SSL certificate error</title></head>
<body bgcolor="white">
<center><h1>496 </h1></center>
<center>客户没有出示所需的证书</center>
<hr><center>OenSSL</center>
</body>
</html>

php Curl 请求示例

CURLOPT_TIMEOUT:超时时间
CURLOPT_RETURNTRANSFER:是否要求返回数据
CURLOPT_SSL_VERIFYPEER:是否检测服务器的证书是否由正规浏览器认证过的授权CA颁发的
CURLOPT_SSL_VERIFYHOST:是否检测服务器的域名与证书上的是否一致
CURLOPT_SSLCERTTYPE:证书类型,"PEM" (default), "DER", and"ENG".
CURLOPT_SSLCERT:证书存放路径
CURLOPT_SSLCERTPASSWD:证书密码,没有可以留空
CURLOPT_SSLKEYTYPE:私钥类型,"PEM" (default), "DER", and"ENG".
CURLOPT_SSLKEY:私钥存放路径
'allow_self_signed'=>true,  // 是否允许自签名证书
'cafile'=>'ca.crt',         // 根证书
*/
function curl_post_ssl($url, $vars, $second = 30, $aHeader = array())
{
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_TIMEOUT, $second);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_SSLCERT, './client.crt');
    curl_setopt($ch, CURLOPT_SSLKEY, './client.key');
    curl_setopt($ch, CURLOPT_HTTPHEADER, $aHeader);

    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); //允许302

    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $vars);
    $data = curl_exec($ch);
    if (!curl_errno($ch)) {
        $info = curl_getinfo($ch);
    } else {
        echo 'Curl error: ' . curl_error($ch);
    }
    curl_close($ch);
    return $data;
}

浏览器配置

导入客户端证书

导入到证书 个人区域即可(导入成功个人区域可见,否则可能是证书或者密码错误)

宝塔docker 域名映射提示域名已经存在 宝塔多域名证书_html


完成后 打开浏览器访问网页即可 (刷新可能无效,如打开请先关闭)

如果是自签CA发布需要将CA证书导入到受信任的根证书颁发机构

宝塔docker 域名映射提示域名已经存在 宝塔多域名证书_ssl_02


此时浏览器将不再提示证书不受信任

shell 辅助脚本

生成CA证书以及CA目录
  • create_ca_cert.sh 仅首次运行
  • 生成后需要修改nginx配置文件
ssl_client_certificate ssl_certs/demoCA/cacert.pem;
 ssl_crl ssl_certs/demoCA/private/ca.crl;
 ssl_verify_client on;
#!/bin/bash -e

# 创建CA根证书
# 非交互式方式创建以下内容:
# 国家名(2个字母的代号)
C=CN
# 省
ST=Shannxi
# 市
L=Xian
# 公司名
O=My Company
# 组织或部门名
OU=技术部
# 服务器FQDN或颁发者名
CN=www.example.com
# 邮箱地址
emailAddress=admin@example.com

mkdir -p ./demoCA/{private,newcerts}
touch ./demoCA/index.txt
[ ! -f ./demoCA/seria ] && echo 01 > ./demoCA/serial
[ ! -f ./demoCA/crlnumber ] && echo 01 > ./demoCA/crlnumber
[ ! -f ./demoCA/cacert.pem ] && openssl req -utf8 -new -x509 -days 36500 -newkey rsa:2048 -nodes -keyout ./demoCA/private/cakey.pem -out ./demoCA/cacert.pem -subj "/C=${C}/ST=${ST}/L=${L}/O=${O}/OU=${OU}/CN=${CN}/emailAddress=${emailAddress}"
[ ! -f ./demoCA/private/ca.crl ] && openssl ca -crldays 36500 -gencrl -out "./demoCA/private/ca.crl"
创建客户端证书,并用CA证书签证
  • create_client_cert.sh
#!/bin/bash -e

show_help() {
  echo "$0 [-h|-?|--help] [--ou ou] [--cn cn] [--email email]"
  echo "-h|-?|--help  显示帮助"
  echo "--ou      设置组织或部门名,如: 技术部"
  echo "--cn      设置FQDN或所有者名,如: 冯宇"
  echo "--email     设置FQDN或所有者邮件,如: fengyu@example.com"
}

while [[ $# -gt 0 ]]
do
  case $1 in
    -h|-\?|--help)
      show_help
      exit 0
      ;;
    --ou)
      OU="${2}"
      shift
      ;;
    --cn)
      CN="${2}"
      shift
      ;;
    --email)
      emailAddress="${2}"
      shift
      ;;
    --)
      shift
      break
    ;;
    *)
      echo -e "Error: $0 invalid option '$1'\nTry '$0 --help' for more information.\n" >&2
      exit 1
    ;;
  esac
shift
done

# 创建客户端证书
# 非交互式方式创建以下内容:
# 国家名(2个字母的代号)
C=CN
# 省
ST=Shannxi
# 市
L=Xian
# 公司名
O=My Company
# 组织或部门名
OU=${OU:-测试部门}
# 服务器FQDN或授予者名
CN=${CN:-demo}
# 邮箱地址
emailAddress=${emailAddress:-demo@example.com}

mkdir -p "${CN}"

[ ! -f "${CN}/${CN}.key" ] && openssl req -utf8 -nodes -newkey rsa:2048 -keyout "${CN}/${CN}.key" -new -days 36500 -out "${CN}/${CN}.csr" -subj "/C=${C}/ST=${ST}/L=${L}/O=${O}/OU=${OU}/CN=${CN}/emailAddress=${emailAddress}"
[ ! -f "${CN}/${CN}.crt" ] && openssl ca -utf8 -batch -days 36500 -in "${CN}/${CN}.csr" -out "${CN}/${CN}.crt"
[ ! -f "${CN}/${CN}.p12" ] && openssl pkcs12 -export -clcerts -CApath ./demoCA/ -inkey "${CN}/${CN}.key" -in "${CN}/${CN}.crt" -certfile "./demoCA/cacert.pem" -passout pass: -out "${CN}/${CN}.p12"
吊销一个签证过的证书
  • revoke_cert.sh
openssl ca -revoke "${1}/${1}.crt"
openssl ca -gencrl -out "./demoCA/private/ca.crl"