GmSSL自助式域名证书服务脚本指南
一、概述
GmSSL作为开源的国密密码工具箱,提供了完整的国密算法实现和命令行工具。对于需要自动化申请国密SM2域名证书的场景,目前主要有两条技术路线:
1. GmSSL原生自助服务脚本:使用GmSSL自带的`gmca`工具和自助域名证书服务
2. 国密ACME客户端:基于ACME规范的自动化证书管理方案
本文重点介绍方案一——利用GmSSL内置工具编写自助式域名证书申请脚本。
GmSSL自助式域名证书服务的核心机制是:管理员在Web服务器上运行客户端脚本,自动完成注册、证书申请,并获取由自助证书服务签发的国密SM2域名证书,签发的证书可用于集成了GmSSL的Apache、Nginx等主流Web服务器。
二、GmSSL核心工具与命令
2.1 gmca工具命令
`gmca`是GmSSL内置的管理员工具,用于创建和维护简易CA,可以大幅简化根证书创建、签发证书、证书管理等工作。主要命令包括:
命令 功能说明
`gmca --setup` CA初始化,生成CA根证书、私钥及数据库
`gmca --cacert` 显示CA根证书
`gmca --gencsr <CommonName>` 生成CSR
`gmca --listcsrs` 显示所有未签发的CSR
`gmca --signcsr <CommonName>` 对CSR签名,生成证书
`gmca --listcerts` 显示所有已签发的证书
`gmca --revokecertbyserial <SerialNumber>` 通过序列号注销证书
`gmca --gencrl` 生成证书注销列表
2.2 基础SM2证书生成命令
bash
# 生成SM2密钥对(EC参数)
gmssl ecparam -genkey -name sm2p256v1 -out server.key
# 从私钥导出公钥
gmssl ec -in server.key -pubout -out server.pub
# 生成证书签名请求(CSR)
gmssl req -new -sm3 -key server.key -out server.csr
# 自签名证书
gmssl req -new -x509 -days 365 -key server.key -out server.crt
# 查看证书信息
gmssl x509 -in server.crt -noout -text
2.3 国密双证书生成(签名证书+加密证书)
国密TLCP协议要求签名和加密密钥分离,需要生成两个SM2证书:一个用于签名(digitalSignature),一个用于加密(keyEncipherment)。
bash
# 配置签名证书的扩展属性(v3_red)
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature
# 配置加密证书的扩展属性(v3enc_red)
basicConstraints = CA:FALSE
keyUsage = keyAgreement, keyEncipherment, dataEncipherment
三、自助式域名证书申请脚本
3.1 脚本核心结构
以下是一个完整的自助式国密证书申请脚本框架:
bash
!/bin/bash
文件名: gmssl_cert_auto.sh
功能: GmSSL自助式域名证书服务脚本
用途: 自动完成国密SM2证书的申请、签发和管理
set -e
---------- 配置参数 ----------
CERT_PATH="/opt/gmssl/certs" # 证书存储目录
PRIVATE_PATH="/opt/gmssl/private" # 私钥存储目录
CA_DIR="/opt/gmssl/ca" # CA目录
CONFIG_FILE="/opt/gmssl/openssl.cnf" # 配置文件路径
DOMAIN="" # 域名(由命令行传入)
DAYS=365 # 证书有效期
ENABLE_DEBUG=0 # 调试模式(0关闭,1开启)
---------- 颜色输出 ----------
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
---------- 检查函数 ----------
check_requirements() {
log_info "检查环境依赖..."
# 检查GmSSL是否安装
if ! command -v gmssl &> /dev/null; then
log_error "GmSSL未安装,请先安装GmSSL"
fi
# 获取GmSSL版本
gmssl version 2>&1 | head -1 | xargs log_info
# 创建必要目录
mkdir -p "$CERT_PATH" "$PRIVATE_PATH" "$CA_DIR"
log_info "环境检查通过"
}
---------- CA初始化函数 ----------
init_ca() {
if [ -f "$CA_DIR/ca.key" ]; then
log_warn "CA已存在,跳过初始化"
return 0
fi
log_info "初始化CA..."
cd "$CA_DIR"
# 生成CA私钥
gmssl ecparam -genkey -name sm2p256v1 -out ca.key
chmod 400 ca.key
# 生成CA证书请求
gmssl req -new -sm3 -key ca.key -out ca.csr -subj "/C=CN/ST=BJ/L=Beijing/O=DemoCA/CN=GmSSL Root CA"
# 自签名CA证书
gmssl req -new -x509 -days 3650 -key ca.key -out ca.crt -subj "/C=CN/ST=BJ/L=Beijing/O=DemoCA/CN=GmSSL Root CA"
log_info "CA初始化完成: ca.crt, ca.key"
}
---------- 证书申请函数 ----------
apply_cert() {
local domain="$1"
local cert_file="$CERT_PATH/${domain}.crt"
local key_file="$PRIVATE_PATH/${domain}.key"
if [ -z "$domain" ]; then
log_error "请提供域名"
fi
if [ -f "$cert_file" ] && [ -f "$key_file" ]; then
log_warn "证书 ${domain}.crt 已存在,如需重新申请请先删除"
return 1
fi
log_info "为域名 ${domain} 申请证书..."
# 生成服务器私钥
gmssl ecparam -genkey -name sm2p256v1 -out "$key_file"
chmod 400 "$key_file"
# 生成CSR
gmssl req -new -sm3 -key "$key_file" -out "${CERT_PATH}/${domain}.csr" \
-subj "/C=CN/ST=BJ/L=Beijing/O=DemoOrg/CN=${domain}"
# 使用CA签发证书
gmssl x509 -req -days "$DAYS" -in "${CERT_PATH}/${domain}.csr" \
-CA "$CA_DIR/ca.crt" -CAkey "$CA_DIR/ca.key" -CAcreateserial \
-out "$cert_file" -extensions v3_req
log_info "证书签发完成: $cert_file"
}
---------- 验证证书函数 ----------
verify_cert() {
local domain="$1"
local cert_file="$CERT_PATH/${domain}.crt"
log_info "验证证书: $cert_file"
gmssl verify -verbose -CAfile "$CA_DIR/ca.crt" "$cert_file"
# 显示证书详情
gmssl x509 -in "$cert_file" -noout -text | grep -E "Subject:|Not Before|Not After"
}
---------- 双证书申请(签名+加密)----------
apply_dual_certs() {
local domain="$1"
log_info "申请国密双证书(签名证书+加密证书)..."
# 签名证书
apply_cert_sign "$domain"
# 加密证书
apply_cert_enc "$domain"
log_info "双证书申请完成"
}
apply_cert_sign() {
local domain="$1"
local key_file="${PRIVATE_PATH}/${domain}_sign.key"
local cert_file="${CERT_PATH}/${domain}_sign.crt"
gmssl ecparam -genkey -name sm2p256v1 -out "$key_file"
gmssl req -new -sm3 -key "$key_file" -out "${CERT_PATH}/${domain}_sign.csr" \
-subj "/C=CN/ST=BJ/L=Beijing/O=DemoOrg/CN=${domain}"
# 签名证书使用v3_red扩展(digitalSignature)
gmssl x509 -req -days "$DAYS" -in "${CERT_PATH}/${domain}_sign.csr" \
-CA "$CA_DIR/ca.crt" -CAkey "$CA_DIR/ca.key" -CAcreateserial \
-out "$cert_file" -extensions v3_red
}
apply_cert_enc() {
local domain="$1"
local key_file="${PRIVATE_PATH}/${domain}_enc.key"
local cert_file="${CERT_PATH}/${domain}_enc.crt"
gmssl ecparam -genkey -name sm2p256v1 -out "$key_file"
gmssl req -new -sm3 -key "$key_file" -out "${CERT_PATH}/${domain}_enc.csr" \
-subj "/C=CN/ST=BJ/L=Beijing/O=DemoOrg/CN=${domain}"
# 加密证书使用v3enc_red扩展(keyEncipherment)
gmssl x509 -req -days "$DAYS" -in "${CERT_PATH}/${domain}_enc.csr" \
-CA "$CA_DIR/ca.crt" -CAkey "$CA_DIR/ca.key" -CAcreateserial \
-out "$cert_file" -extensions v3enc_red
}
---------- 续期函数 ----------
renew_cert() {
local domain="$1"
local old_cert="$CERT_PATH/${domain}.crt"
if [ ! -f "$old_cert" ]; then
log_error "旧证书不存在,请先申请证书"
fi
log_info "续期证书: ${domain}"
# 备份旧证书
mv "$old_cert" "${old_cert}.bak"
# 重新申请证书(保留原有私钥)
local key_file="$PRIVATE_PATH/${domain}.key"
if [ ! -f "$key_file" ]; then
log_error "私钥文件丢失,无法续期"
fi
# 重新生成CSR
gmssl req -new -sm3 -key "$key_file" -out "${CERT_PATH}/${domain}.csr" \
-subj "/C=CN/ST=BJ/L=Beijing/O=DemoOrg/CN=${domain}"
# 重新签发
gmssl x509 -req -days "$DAYS" -in "${CERT_PATH}/${domain}.csr" \
-CA "$CA_DIR/ca.crt" -CAkey "$CA_DIR/ca.key" -CAcreateserial \
-out "$old_cert"
log_info "续期完成"
}
# ---------- 证书列表函数 ----------
list_certs() {
log_info "已签发的证书列表:"
for cert in "$CERT_PATH"/*.crt; do
if [ -f "$cert" ]; then
local cn=$(gmssl x509 -in "$cert" -noout -subject 2>/dev/null | sed 's/.*CN=//' | cut -d'/' -f1)
local notafter=$(gmssl x509 -in "$cert" -noout -enddate 2>/dev/null | cut -d'=' -f2)
echo " - $cert (CN=$cn, 过期: $notafter)"
fi
done
}
# ---------- 吊销证书 ----------
revoke_cert() {
local domain="$1"
local cert_file="$CERT_PATH/${domain}.crt"
if [ ! -f "$cert_file" ]; then
log_error "证书文件不存在: $cert_file"
fi
log_info "吊销证书: ${domain}"
mv "$cert_file" "${CERT_PATH}/revoked_${domain}.crt"
# 生成CRL
cd "$CA_DIR"
gmssl ca -gencrl -keyfile ca.key -cert ca.crt -out ca.crl
log_info "证书已吊销,CRL已更新"
}
---------- 帮助信息 ----------
show_help() {
cat << EOF
用法: $0 <命令> [选项]
命令:
init 初始化CA(首次使用必须执行)
apply <域名> 申请单证书
dual <域名> 申请国密双证书(签名+加密)
renew <域名> 续期证书
list 列出所有证书
verify <域名> 验证证书
revoke <域名> 吊销证书
help 显示此帮助信息
示例:
$0 init
$0 apply example.com
$0 dual example.com
$0 renew example.com
$0 list
环境变量:
CERT_PATH 证书存储目录(默认: /opt/gmssl/certs)
DAYS 证书有效期天数(默认: 365)
EOF
}
---------- 主函数 ----------
main() {
local cmd="$1"
local domain="$2"
case "$cmd" in
init)
check_requirements
init_ca
;;
apply)
check_requirements
apply_cert "$domain"
;;
dual)
check_requirements
apply_dual_certs "$domain"
;;
renew)
check_requirements
renew_cert "$domain"
;;
list)
check_requirements
list_certs
;;
verify)
check_requirements
verify_cert "$domain"
;;
revoke)
check_requirements
revoke_cert "$domain"
;;
help|--help|-h)
show_help
;;
*)
if [ -z "$cmd" ]; then
show_help
else
log_error "未知命令: $cmd,使用 help 查看帮助"
fi
;;
esac
}
main "$@"
3.2 Nginx部署配置
使用国密双证书的Nginx配置示例:
nginx
server {
listen 443 ssl;
server_name example.com;
# 签名证书
ssl_certificate /opt/gmssl/certs/example.com_sign.crt;
ssl_certificate_key /opt/gmssl/private/example.com_sign.key;
# 加密证书
ssl_certificate /opt/gmssl/certs/example.com_enc.crt;
ssl_certificate_key /opt/gmssl/private/example.com_enc.key;
# 国密加密套件
ssl_ciphers "ECDHE-SM2-SM4-GCM-SM3:ECC-SM2-SM4-GCM-SM3";
ssl_protocols TLSv1.1 TLSv1.2;
location / {
root /var/www/html;
index index.html;
}
}
四、自动化任务配置
4.1 定时续期(Cron)
bash
# 编辑crontab
crontab -e
# 每月1日凌晨2点执行证书续期检查
0 2 1 * * /opt/gmssl/gmssl_cert_auto.sh renew example.com >> /var/log/gmssl_renew.log 2>&1
4.2 批量申请脚本
bash
#!/bin/bash
# 批量申请证书脚本
DOMAINS=("example1.com" "example2.com" "example3.com")
for domain in "${DOMAINS[@]}"; do
/opt/gmssl/gmssl_cert_auto.sh apply "$domain"
done
五、国密ACME替代方案——SM2cerBot
如果需要对接专业的国密证书自动化服务,可以考虑使用国密ACME客户端**SM2cerBot**。
SM2cerBot是基于《自动化证书管理规范》密码行业标准草案V6版本开发的开源ACME客户端,默认对接零信技术提供的国密ACME公共服务,可自动完成证书申请、取回、部署和续期。
使用示例
bash
# 申请证书
./SM2cerBot \
--email=admin@example.com \
--eab \
--kid=admin@example.com \
--hmac=your_token \
--domains=example.com \
--path=/opt/acme \
--http \
run --http.webroot=/opt/acme/webroot \
--run-hook=/opt/acme/hook.sh
Nginx配置(用于HTTP验证)
nginx
location /.well-known/pki-validation/ {
root /opt/acme/webroot;
}
Hook脚本示例
bash
#!/bin/bash
# hook.sh
sudo nginx -s reload
echo "hook file run success"
六、常见问题与注意事项
1. Windows环境:建议使用Git Bash或MSYS2环境来运行脚本,避免直接使用CMD。配置环境变量时将GmSSL的`bin`目录加入PATH。
2. 双证书配置:国密TLCP协议要求签名和加密密钥分离,务必生成并使用双证书,单证书会导致浏览器访问失败。
3. 配置文件:需要正确配置`openssl.cnf`,包括设置`default.md = sm3`以及签名/加密证书的扩展属性。
4. 路径问题:Windows下使用GmSSL时,DN参数写法特殊,需添加`//skip=yes/C=CN/...`。
5. 私钥安全:私钥文件应设置`chmod 400`权限,避免泄露。
七、总结
本文提供的自助式证书脚本封装了GmSSL的核心命令,实现了CA初始化、单/双证书申请、续期、吊销和验证等完整功能。根据具体场景选择:
内网/测试环境:使用上述GmSSL自助脚本,搭建私有CA体系
公网生产环境:推荐使用SM2cerBot等ACME客户端,对接专业CA服务获得浏览器信任的证书
脚本中涉及的命令和参数可根据实际环境进行调整,建议在测试环境验证后再投入生产使用。