前阵子接了个内网项目,要求所有业务系统统一走国密证书,而且不能依赖外部商业CA。一开始以为无非就是装个GmSSL、跑几条命令的事,真正做起来才发现不少细节和坑:CentOS 7上怎么装GmSSL、SM2密钥对和RSA有什么本质区别、证书链文件到底按什么顺序拼、为什么用系统自带的openssl verify死活报错。折腾了几天把整条链路跑通之后,我把实操过程完整复盘了一遍,从环境准备到根证书生成、服务端证书签发、证书链验证,每一步都附上命令和解释,希望能帮你少走弯路。
1. 为什么国密证书非要自己搭一套CA
1.1 国密算法和"支持国密"是两码事
先说个容易被忽略的前提:国密算法指的是我国商用密码算法体系,核心是SM2(非对称)、SM3(摘要)、SM4(对称)一套组合。而国密证书,指的是用SM2作为公钥算法、用SM3作为摘要算法签发的X.509证书。
这里有个常见的误区:很多人以为"GmSSL支持国密"就等于"装上GmSSL就能自动签发国密证书"。其实GmSSL是一个密码库和命令行工具,它提供的是算法能力和PKI命令,但不会替你搭好CA。CA的目录结构、配置文件、签发策略、证书生命周期管理,全都要自己从零搭建。换句话说,GmSSL像是给你一套木工工具,但柜子能不能立起来,取决于你的设计和手艺。
另外一个现实约束是:市面上商业国密CA的证书签发服务不便宜,而且走的是企业实名审核流程,周期往往以工作日计算。对于内网测试环境、私有化部署的项目、或者需要快速验证国密互通性的场景,自己用GmSSL搭一个私有CA是最快也最可控的路径。
1.2 私有CA和商业CA怎么选
内部系统用的国密证书,到底该自己签还是买商业证书,我的建议是分场景:
- 对外服务的公网站点:建议用有合法资质的商业CA签发国密证书,这样才能被主流浏览器和客户端正确信任。自签根的证书装到用户浏览器里会弹信任告警,体验很差。
- 内网业务系统、微服务间通信、企业内部应用:完全可以用私有CA。只要你把根证书下发给所有内部客户端并导入信任库,效果和商业CA没有本质区别,而且签发完全自助、成本为零。
- 等保测评或合规检查场景:很多单位要求国密改造,但对CA是否必须商业并无硬性规定,只要是符合标准格式的国密证书链即可。不过每个地方的验收口径不一样,动手前最好先跟测评方确认清楚。
本文的实操属于第二种,也是绝大多数人真正面临的需求:在CentOS 7上搭一套单层的私有国密CA,然后给内网服务签发国密证书,并组装出完整可验证的证书链。
1.3 这篇实操会覆盖到什么程度
完整流程分四块:一是CentOS 7上编译安装GmSSL;二是初始化CA目录和配置文件,生成SM2根密钥对和自签名根证书;三是签发服务端国密证书;四是验证证书链、排查常见错误。
我会把每一步的原理和坑都讲清楚,尤其是配置文件里每个字段的作用、SM2双证书体系是怎么回事、以及为什么你按网上教程操作会碰到各种报错。文章里用到的所有命令,我都按GmSSL 2.x系列的命令行工具来写,这是目前搭CA最顺手、资料也最多的版本。
2. CentOS 7上编译安装GmSSL的正确姿势
2.1 编译前的依赖准备
CentOS 7自带的OpenSSL版本是1.0.2k,不支持SM2算法,所以不能用它来签发国密证书,必须单独安装GmSSL。GmSSL官方推荐源码编译方式,依赖并不复杂,核心就是gcc、make、Perl这几个基础工具。
先执行系统更新和依赖安装:
yum install -y epel-release yum install -y gcc make perl-core tar wget unzip这里有个小坑:CentOS 7的Perl版本是5.16,对GmSSL的Configure脚本来说足够用了,但如果你的系统是精简安装,缺了perl-CPAN之类的模块,运行Configure时会提示找不到某些Perl模块。所以建议直接一次性装好perl-core,它能覆盖绝大多数模块依赖。
另外提醒一句,如果你用的是内存只有1GB的小机器,编译时可能会比较慢,建议在Configure阶段不要加-j8这种高并发参数,老老实实单线程编译,否则内存不足反而容易莫名其妙断掉。
2.2 下载源码、编译、安装
GmSSL的2.x版本系列在GitHub上的仓库是guanzhi/GmSSL,注意要选择2.x的release分支或tag。为什么特别强调这一点?因为GmSSL在3.x版本做了大量重构,命令行工具的使用方式跟2.x完全不一样,网上绝大多数教程、文档、参考命令都是基于2.x写的,你拿3.x的二进制去跑那些命令,根本跑不通。
我用的是2.5.4版本,安装过程如下:
mkdir -p /opt/src && cd /opt/src # 下载2.5.4的源码包,假设已经拿到本地或wget到 tar zxf GmSSL-2.5.4.tar.gz cd GmSSL-2.5.4 ./config --prefix=/usr/local/gmssl make make install编译结束后,可执行文件在/usr/local/gmssl/bin/gmssl。为了方便全局调用,建议做个软链接:
ln -s /usr/local/gmssl/bin/gmssl /usr/sbin/gmssl然后验证版本:
gmssl version如果能正常输出GmSSL xx.x.x,说明安装成功。
2.3 系统里OpenSSL和GmSSL共存会不会冲突
这是个高频问题。两者二进制名不同,一个叫openssl,一个叫gmssl,安装目录也分开,所以共存没有任何问题。但要注意:gmssl命令默认去读/etc/pki/tls/openssl.cnf这个系统OpenSSL的配置文件吗?答案是不一定。GmSSL在编译时通常会内置默认配置路径,为了稳妥,后面搭CA时我会明确指定自己的配置文件,避免它读到系统配置导致各种不可预期的行为。
还有一点,如果你想在脚本里强制使用gmssl而系统PATH中同时存在/usr/bin/openssl和/usr/local/gmssl/bin,要注意bash的command -v gmssl指向的是不是你自己装的那个。我建议要么用绝对路径调用,要么把软链接放到/usr/local/bin且确认PATH优先级。
2.4 离线环境怎么装
如果你的CentOS 7服务器处于内网隔离环境,没法直接wget源码包和yum下载依赖,可以参考离线部署Docker的思路:找一台有网络的同版本CentOS机器,执行yum install --downloadonly --downloaddir=/tmp/rpms gcc make perl-core把依赖rpm全部拉下来,再拷到内网用rpm -Uvh *.rpm安装。源码包同样在有网机器上下好,连同依赖一起拷贝进内网,然后按正常流程编译。整个过程跟离线安装Docker的套路完全一致,核心原则就是"提前准备好全部依赖包,一次性拷入"。
还有个小技巧:如果内网机器连Perl模块都缺得很厉害,可以考虑编译时加--no-ec2m之类的选项来精简特性,但不建议在没搞清依赖链前就乱加flag,容易编译出功能不完整的版本。老老实实把依赖装齐,成本最低。
3. 私有CA初始化:目录、配置与根证书生成
3.1 证书链的基本结构
在动手之前,得先搞明白"完整证书链"到底是个什么概念。X.509证书体系里的证书链,描述的是一个信任传递关系:客户端信任根CA,根CA给中间CA签发证书,中间CA再给业务系统签发证书,由此形成一个链条。验证时从业务证书开始,逐级向上验证,直到找到客户端信任的根证书。
私有CA最常见的形态是单层结构:根CA直接签发服务端证书,链条只有两级。如果你的企业内部组织复杂,想按部门隔离签发权限,可以再搭建中间CA,变成三级链。本文先讲两级链,因为这是最基础也最通用的模型,理解了它,扩展到三级链只是多一步签发中间CA的操作。
3.2 搭建CA目录骨架
GmSSL的ca命令在设计上模仿了OpenSSL的经典CA体系,需要一个固定的目录结构来存放证书数据库、序列号、新签发证书和私钥。我习惯把整个CA工作目录放在/opt/gmca,里面建一个demoCA作为CA的默认home:
mkdir -p /opt/gmca/demoCA/{newcerts,crl,private} touch /opt/gmca/demoCA/index.txt echo 1000 > /opt/gmca/demoCA/serial这几个文件和目录的含义说一下:
index.txt:CA的证书数据库,文本格式,每一行记录一张已签发证书的序列号、吊销状态、DN等信息。初始是空文件。serial:记录下一个证书要用的序列号。刚才写入的1000是十六进制数,签第一张证书时序列号就是1000,签完自动加一。newcerts:存放CA签发出的所有证书副本。crl:证书吊销列表目录,后续如果要做证书吊销会用到。private:根CA私钥存放目录,权限必须严格控制。
然后单独建一个配置文件目录,把GmSSL用的CA配置放进去:
mkdir -p /opt/gmca/conf3.3 生成SM2根密钥对
国密非对称算法SM2使用的椭圆曲线国标是sm2p256v1,和OpenSSL里常见的prime256v1(也就是P-256)不是同一条曲线。生成密钥对的时候必须显式指定曲线名称,这是最常见的出错点:有些人用默认曲线生成密钥,后面签名时GmSSL直接报错或者签出来的证书根本没法验证。
生成根CA密钥对的命令:
cd /opt/gmca gmssl ecparam -genkey -name sm2p256v1 -out demoCA/private/cakey.pem执行完检查一下密钥格式:
gmssl ec -in demoCA/private/cakey.pem -text -noout如果输出里能看到ASN1 OID: sm2p256v1这样的信息,说明密钥曲线正确,可以用来做国密签名。
注意:SM2的私钥文件里其实同时包含了对应的公钥信息,这一点和RSA私钥只包含私钥参数不同。所以后续用这个私钥直接就能提取出公钥做验证,不需要单独再生成一份公钥文件。这算是SM2的一个小特点,习惯RSA的人一开始可能会疑惑。
3.4 编写CA配置文件并生成自签名根证书
GmSSL的ca命令和req命令都需要读配置文件。这里我给出一份能直接用的最小配置,放在/opt/gmca/conf/gmssl.cnf:
[ ca ] default_ca = CA_default [ CA_default ] dir = /opt/gmca/demoCA database = $dir/index.txt serial = $dir/serial new_certs_dir = $dir/newcerts private_key = $dir/private/cakey.pem certificate = $dir/cacert.pem default_days = 3650 default_md = sm3 policy = policy_any [ policy_any ] countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional commonName = supplied emailAddress = optional [ req ] distinguished_name = req_distinguished_name prompt = no [ req_distinguished_name ] countryName = CN stateOrProvinceName = Beijing localityName = Beijing organizationName = Example Corp organizationalUnitName = IT Dept commonName = Example Root CA几个关键字段的作用说明:
default_md = sm3:指定摘要算法为SM3。这是签发国密证书的核心配置,漏了这一项,后面的ca签名命令默认会走SHA256,签出来的证书就不是国密证书。policy_any:签发策略,意思是CSR请求里的DN字段可以省略,但commonName必须提供。如果你希望严格要求申请者填写的国家、组织等信息与CA一致,可以改成policy_match,但内网私有CA没必要这么严格。dir:CA工作目录,必须和上面建的目录完全一致,否则ca命令会抱怨找不到newcerts目录。
配置就绪后,生成自签名根证书:
cd /opt/gmca gmssl req -new -x509 -key demoCA/private/cakey.pem -out demoCA/cacert.pem -days 3650 -config conf/gmssl.cnf -sm3注意命令行里加了-sm3,这是GmSSL命令行用来显式指定SM3摘要的选项,和配置文件里的default_md双保险。-x509表示直接生成自签名证书,-days 3650设置根证书有效期10年。
生成完立刻查看证书关键信息:
gmssl x509 -in demoCA/cacert.pem -text -noout重点看两处:Signature Algorithm一栏应该是sm3WithSM2,Public Key Algorithm一栏应该是id-ecPublicKey且曲线是sm2p256v1。看到这两个关键信息,说明根证书确实是国密证书,这一步就算走对了。
根证书生成后,建议立刻把私钥备份一份到离线存储,然后在当前机器上收紧权限:
chmod 600 demoCA/private/cakey.pem chattr +i demoCA/private/cakey.pemchattr +i是给文件加不可修改标志,防止被意外覆盖或删除。后面如果要更新根证书或私钥,需要先chattr -i解掉。这个习惯在正式环境非常有用。
4. 签发服务端国密证书:完整CA操作实战
4.1 生成服务端SM2密钥对
根证书就绪后,开始给具体的业务域名或IP签发证书。先建一个目录存放待签发的服务端相关文件:
mkdir -p /opt/gmca/server cd /opt/gmca/server生成服务端SM2密钥对:
gmssl ecparam -genkey -name sm2p256v1 -out server.key这里和生成根密钥的命令完全一样,只是换了输出文件名。有同学会问,根CA和服务端能用同一把密钥吗?答案是不建议也不能。每张证书的密钥对应该独立生成,这样才能保证即使服务端私钥泄露,也不会波及根CA的安全。
4.2 生成证书签发请求
用服务端私钥生成CSR,也就是证书签发请求。这里要注意-subj参数的写法,格式是/C=CN/ST=Beijing/L=Beijing/O=Example/OU=IT/CN=server.example.com,其中CN必须填服务实际使用的域名或IP,这是客户端校验证书身份的关键字段。
cd /opt/gmca/server gmssl req -new -key server.key -out server.csr -subj "/C=CN/ST=Beijing/L=Beijing/O=Example/OU=IT/CN=server.example.com" -config /opt/gmca/conf/gmssl.cnf生成后可以检查CSR内容:
gmssl req -in server.csr -text -noout确认里面Subject字段是你填的域名,Public Key Algorithm是SM2,然后进入签发环节。
如果CSR生成时报unable to load config或者找不到默认配置的错,多半是GmSSL在编译时的默认配置路径与当前系统不一致。解决办法就是像我这样在命令行里用-config显式指定配置文件路径,一劳永逸。
4.3 用CA签发服务端证书
签发证书的命令如下:
cd /opt/gmca/server gmssl ca -in server.csr -out server.crt -config /opt/gmca/conf/gmssl.cnf -days 730 -batch这里加-batch参数,表示跳过交互确认,否则执行时会弹出一个"Sign the certificate? [y/N]"的询问,脚本化部署时容易被卡住。-days 730让服务端证书有效期两年,你可以按自己需求调整,一般内部证书一年或两年都常见。
签发过程中,GmSSL会往index.txt里追加一条记录,同时把证书副本放到newcerts目录。如果一切顺利,屏幕上会显示签名成功的信息。
签完之后检查服务端证书:
gmssl x509 -in server.crt -text -noout同样重点看签名算法是不是sm3WithSM2、公钥曲线是不是sm2p256v1。再看Issuer字段是不是刚才生成的Example Root CA,Subject字段是不是你填的server.example.com。两者都对,说明证书签发成功,而且确实是由你的私有CA签发的。
4.4 关于国密双证书体系的重要提醒
这里有个很多人到后期才发现的坑:国密SSL(GMTLS,即GB/T 38636-2020定义的传输层密码协议)规范要求通信双方使用双证书体系,也就是每个实体要同时持有签名证书和加密证书两套证书。签名证书用签名私钥进行身份认证和握手签名,加密证书用加密私钥做密钥协商或数据加密。
GmSSL命令行在生成SM2密钥对时,默认生成的私钥既可以做签名也可以做加密,但这只是密钥层面的能力。到了真正部署国密SSL网关或浏览器时,服务端需要配置两份证书链、两份私钥:
- 签名证书链 + 签名私钥
- 加密证书链 + 加密私钥
所以在生产环境,一般需要为同一个域名生成两套独立的SM2密钥对,分别走一遍CSR和签发流程,得到签名证书和加密证书。如果是做应用层的国密签名验签(比如SDK签名、电子签章),那么一对密钥就够了,不必强行上双证书。
本文为了演示简化,只签发了一张服务端证书。如果你想完整落地国密HTTPS,建议马上再按同样流程给同一域名生成第二张证书,用于加密场景,命令只需要把文件名换成server_enc.key和server_enc.csr即可。
5. 证书链的组装、验证与常见坑
5.1 证书链文件的正确顺序
单张服务端证书签出来后,还不能直接丢给服务端用。客户端在验证时需要一个完整的证书链,按"叶子证书在上、根证书在下"的顺序拼接。拿我们这种两级链来说,证书链文件就是服务端证书和根证书按顺序拼在一起:
cd /opt/gmca/server cat server.crt /opt/gmca/demoCA/cacert.pem > server_chain.pem为什么顺序不能反?因为TLS握手时,服务端会把证书链发给客户端,客户端解析第一张证书作为站点证书,然后用后续证书作为信任锚来建立信任路径。如果第一张是根证书,客户端就分不清你到底要它信任谁,验证直接失败。这个顺序问题在Nginx、Tomcat等服务器上配置证书时表现得特别明显,很多人遇到sslv3 alert certificateunknown这类报错,八成就是证书链顺序不对。
另外有同学会问,要不要把根证书也拼在服务端证书链里发给客户端?严格说,根证书通常应该通过客户端的信任库预置,不必也不应该随业务证书下发。但在私有CA环境下,很多内网客户端还没来得及导入你的根证书,此时把根证书拼进去可以做到"开箱即验"。我个人的做法是:内部快速验证时可以拼上根证书,正式环境里还是让客户端预置根证书更规范。
5.2 用gmssl验证证书链
验证命令很简单:
cd /opt/gmca/server gmssl verify -CAfile /opt/gmca/demoCA/cacert.pem server_chain.pem如果输出OK,说明从服务端证书到根证书之间的信任路径一路验证通过。如果只拿服务端证书验证,不带根证书,则必须用-untrusted参数指定中间证书,这里因为是两级链,直接-CAfile指根证书就行。
还有一个更严格的验证方式:指定-purpose sslserver,检查证书的扩展用途是否适合SSL服务端。国密证书在签发时如果要用于SSL服务器,最好在CSR或配置里加上extendedKeyUsage = serverAuth。GmSSL 2.x在直接用ca命令签发时,默认扩展策略不一定包含这个字段。如果你后续要部署Tengine或其他国密SSL网关,建议在配置文件里增加扩展段并在签发时带上。
5.3 常见错误与排查对照表
实操中我遇到过的问题集中在下面几类,列个表方便你对照排查:
| 错误现象 | 原因 | 解决方法 |
|---|---|---|
verify时报unable to get local issuer certificate | 验证时没指定正确的根证书,或根证书不匹配 | 确认-CAfile指向的是签发该证书的根CA的cacert.pem |
证书签名算法显示sha256WithRSAEncryption | 生成或签发时没启用SM3,走了默认摘要算法 | 检查配置文件default_md = sm3,并确认签发命令加了-sm3 |
| 密钥生成时报曲线不支持 | 曲线名称写错,或底层ECC实现没包含SM2曲线 | 统一使用sm2p256v1,不要用prime256v1 |
ca命令报I am unable to access the ./demoCA/newcerts directory | 配置文件里dir路径与实际目录不一致 | 检查dir指向,用绝对路径最稳妥 |
证书验证报certificate has expired | 系统时间不对,或证书确实过期 | date先看系统时间,再查证书有效期 |
| 用系统openssl verify国密证书报错 | CentOS 7自带的OpenSSL 1.0.2k不支持SM2算法 | 改用gmssl verify,验证工具与签名工具必须同源 |
| 部署后发现TLS握手提示证书类型不匹配 | 服务端配置了签名证书,但国密SSL要求双证书 | 补签加密证书,并在服务器上配置加密证书链和私钥 |
这里要特别强调最后一个坑:千万不要用openssl来验证gmssl签发的国密证书。CentOS 7默认自带的OpenSSL 1.0.2k根本不认识SM2算法,你用openssl verify去查,百分百报错。这个"报错"并不代表证书有问题,只是工具不支持。这是一个极其容易误判的问题,我见过有人折腾了大半天以为是证书问题,最后发现只是验证工具选错了。
5.4 部署到服务端的注意事项
拿到证书链和私钥后,在服务端部署时还需要注意几件事:
第一,证书链文件和私钥文件的权限要收紧,私钥尤其重要,建议chmod 600。
第二,如果用Nginx但需要支持国密SSL,普通的官方Nginx默认不支持GMTLS协议,需要替换成Tengine或者安装国密补丁版本。Tengine配置国密双证书时,指令大致是:
server { listen 443 ssl; ssl_certificate /opt/gmca/server/server_chain.pem; ssl_certificate_key /opt/gmca/server/server.key; ssl_enc_certificate /opt/gmca/server/server_enc_chain.pem; ssl_enc_certificate_key /opt/gmca/server/server_enc.key; }第三,如果你只是做应用层的国密签名验签,比如后端服务调用链路上做报文签名,那不需要Tengine这套,直接用GmSSL的SDK或命令行工具在应用里做签名和验签就行。先把证书链验证通过,再在代码里实现对证书的解析和公钥提取,逻辑会清晰很多。
最后说一个生产环境的建议:根证书私钥尽量离线保管,签发操作放在一台专用机器上,不要把根私钥拷到每一台业务服务器。业务服务器只需要持有自己的服务端私钥和证书链。这套权限分离的思路,和你在做任何PKI体系建设时应该坚持的原则完全一致。我在实际项目里踩过不少坑,最值得记住的就一条:国密证书验证必须全程使用GmSSL工具链,一旦混用了系统OpenSSL,所有排查方向都会被带偏。希望这篇实战记录能帮你把整个流程一次跑通。