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服务获得浏览器信任的证书

脚本中涉及的命令和参数可根据实际环境进行调整,建议在测试环境验证后再投入生产使用。