1. 项目概述:为什么在 CentOS 8 上亲手搭建一个 CA 是硬核运维的分水岭
“Создание и настройка центра сертификации (ЦС) в CentOS 8”——这个俄语标题直译过来就是“在 CentOS 8 上创建并配置证书颁发机构(CA)”。它不是一条简单的命令行练习,而是一道横亘在系统管理员、安全工程师和网络架构师职业成长路径上的真实门槛。我带过不少刚从学校出来的新人,他们能熟练部署 Nginx、写 Shell 脚本、甚至玩转 Ansible,但一旦被要求“给内网所有服务签发一套可信证书”,立刻卡壳。问题不在于不会敲openssl req,而在于根本没搞懂:证书链怎么信任?私钥为什么必须离线?CRL 和 OCSP 到底谁在查谁?这些问题的答案,全藏在亲手搭建一个 CA 的每一步里。
CentOS 8(尤其是其主流分支 CentOS Stream 8)是企业级 Linux 环境中承上启下的关键版本。它告别了传统的systemd与firewalld粗放式管理,引入了更严格的 SELinux 策略、模块化软件仓库(AppStream),以及对现代加密套件的原生支持。这意味着,在 CentOS 8 上建 CA,你面对的不是教科书里那个“生成 key + csr + crt 就完事”的玩具环境,而是要直面真实生产环境中的三重约束:策略合规性(SELinux)、服务隔离性(systemd unit 依赖)、以及密码学严谨性(TLS 1.2+ 强制要求)。那些网上随手搜到的“三分钟 OpenSSL CA 教程”,在 CentOS 8 上大概率会栽在Permission denied的报错里,或者因为openssl.cnf配置项缺失导致签发的证书被 Chrome 直接标记为“不安全”。
关键词里的Easy-RSA并非可有可无的“快捷键”。它是 OpenVPN 社区打磨多年的一套 CA 管理脚本框架,本质是把 OpenSSL 底层命令封装成./easyrsa build-ca、./easyrsa gen-req server这样语义清晰的操作。但它的价值远不止于省几行命令——它强制你遵循 PKI(公钥基础设施)的最佳实践:CA 私钥默认离线存储、每次签发都生成唯一序列号、自动维护证书吊销列表(CRL)文件结构。而PKI本身,就是整个项目的核心骨架。它不是某个工具,而是一套规则体系:谁来信任谁?信任的边界在哪里?证书过期了怎么办?吊销了怎么通知客户端?这些抽象概念,只有当你亲手为一台测试用的 Apache 服务器签发证书,并用curl --cacert /path/to/ca.crt https://test.internal成功访问时,才真正落地为肌肉记忆。
至于OpenSSL,它在这里的角色是“引擎”,而非“方向盘”。CentOS 8 默认安装的是 OpenSSL 1.1.1k,它原生支持 X25519 密钥交换、Ed25519 签名算法,但默认配置文件openssl.cnf却沿用了老旧的 SHA-1 摘要算法模板。这就埋下了第一个坑:如果你不手动修改default_md = sha256,签发的证书在现代浏览器里会被降级显示。所以,这个项目真正的技术含量,不在于你会不会用 OpenSSL,而在于你能否读懂它的配置逻辑、理解每个字段背后的密码学含义,并在 CentOS 8 的特定约束下,让整套 PKI 流程像瑞士钟表一样严丝合缝地运转。它解决的不是一个“能不能”的问题,而是一个“敢不敢”的问题——敢不敢把核心业务系统的 TLS 信任锚点,交到自己亲手构建的 CA 手里。
2. 整体设计与思路拆解:为什么放弃一键脚本,坚持手工编排 CA 目录树
在动手之前,我必须坦白:网上确实存在大量“一键部署 CA”的 Ansible Role 或 Shell 脚本,它们能在 30 秒内完成所有操作。但我坚持采用纯手工、目录树驱动的方式搭建,原因非常现实——生产环境的可审计性与故障回溯能力,永远比部署速度重要十倍。一个由脚本自动生成的/etc/pki/CA目录,其内部文件权限、属主、SELinux 上下文往往是混乱的。当某天凌晨三点,监控告警显示“CA 服务不可用”,而你面对的是一个被脚本改得面目全非的配置文件时,那种绝望感,我亲身经历过三次。
因此,我的整体设计思路非常明确:以 OpenSSL 原生命令为基石,以 Easy-RSA 为流程控制器,以 CentOS 8 的标准目录规范为容器,构建一个完全透明、可验证、可迁移的 CA 系统。具体来说,整个方案分为三个物理隔离层:
第一层是离线根 CA(Offline Root CA)。它不运行任何服务,甚至不联网。它的唯一使命,就是在首次初始化时生成一对 4096 位 RSA 密钥,并签发一张有效期长达 20 年的自签名根证书。这把密钥将被刻录在 USB 加密盘上,锁进保险柜。所有后续操作,包括中间 CA 的签发,都通过离线方式完成。这是 PKI 信任链的绝对起点,也是整个方案安全性的基石。CentOS 8 的fapolicyd(文件访问策略守护进程)会天然阻止未授权进程读取该密钥,这反而成了我们的盟友。
第二层是在线中间 CA(Online Intermediate CA)。它才是日常签发证书的主力。我们会在一台专用的 CentOS 8 虚拟机(vm安装centos 8)上部署它,严格遵循最小权限原则:/etc/pki/CA目录的属主设为root:caadmin,caadmin组成员仅包含两名经过双因素认证的管理员;private/子目录的权限被锁定为0700,且 SELinux 类型强制为cert_t;所有日志输出到journald并启用logrotate按日轮转。最关键的是,它的私钥绝不存放在与 Web 服务同台的机器上——这是防止私钥因应用漏洞泄露的铁律。
第三层是证书生命周期管理(CLM)。这并非一个独立服务,而是嵌入在 Easy-RSA 工作流中的自动化检查点。例如,每次执行./easyrsa sign-req server nginx-web时,脚本会自动调用openssl x509 -in nginx-web.crt -text -noout | grep "Not After"提取证书到期时间,并与预设的 365 天阈值比对;若剩余有效期不足 90 天,则拒绝签发并抛出错误。这种“预防性拦截”,比事后写个监控脚本去扫证书文件要可靠得多。
为什么选择 Easy-RSA 而非直接裸用 OpenSSL?答案在于它的状态机设计。OpenSSL 本身是无状态的,你每次调用openssl ca都需要显式指定-config、-keyfile、-cert等参数,极易出错。而 Easy-RSA 通过pki/vars文件固化了所有环境变量,并用pki/index.txt文件作为轻量级证书数据库,记录每张证书的序列号、状态(V=valid, R=revoked)、签发时间等元数据。这使得./easyrsa revoke nginx-web这样的操作,背后其实是原子性地更新index.txt、生成新的 CRL 文件、并重新哈希 CRL 分发点 URL。这种设计,完美契合了 CentOS 8 对服务可靠性的高要求。
最后,关于 CentOS 8 Stream 的选型。虽然它被标为“滚动发布”,但其AppStream仓库中的openssl、pki-core等包,经过 Red Hat 工程师的严格 QA,稳定性远超社区版。更重要的是,它原生支持mod_ssl的SSLOptions +StdEnvVars,这让 Apache 在处理客户端证书时,能直接将证书主题信息注入 CGI 环境变量,为后续的基于证书的单点登录(SSO)打下基础。这正是“配置pki实验”走向真实业务场景的关键一跃。
3. 核心细节解析与实操要点:从 openssl.cnf 配置到 SELinux 上下文的深度控制
真正拉开专业与业余差距的,从来不是宏观架构,而是那些藏在配置文件角落里的魔鬼细节。在 CentOS 8 上搭建 CA,有三个核心细节必须死磕到底:openssl.cnf的定制化改造、证书请求(CSR)中 SAN(Subject Alternative Name)字段的强制注入、以及 SELinux 对证书文件的细粒度管控。忽略其中任何一个,你的 CA 都可能在关键时刻掉链子。
3.1 openssl.cnf:不只是模板,而是 PKI 的宪法
CentOS 8 自带的/etc/pki/tls/openssl.cnf是一个功能完备但面向通用场景的模板。直接使用它,会导致签发的证书缺少现代浏览器强制要求的扩展字段。我们必须对其进行外科手术式的修改。重点改造以下三处:
第一,默认摘要算法升级。在[ ca ]段落下方,找到default_md = default这一行,将其改为default_md = sha256。这看似简单,但影响深远。SHA-1 已被 Chrome、Firefox 等主流浏览器彻底弃用,任何使用 SHA-1 签名的证书都会触发红色警告页。而default_md控制着openssl ca命令对证书签名时使用的哈希算法,它不等于 CSR 中的签名算法(那是openssl req的-sha256参数控制的),而是 CA 对最终证书进行数字签名时的算法。实测中,如果此处遗漏,即使 CSR 用了 SHA-256,签发的证书仍会被标记为不安全。
第二,强制 SAN 字段注入。现代 TLS 要求证书必须包含 SAN,否则无法通过主机名验证。但 OpenSSL 默认的req_extensions并不包含 SAN。我们需要在[ req ]段落中添加req_extensions = req_ext,然后在文件末尾新增[ req_ext ]段落:
[ req_ext ] subjectAltName = @alt_names [ alt_names ] DNS.1 = example.com DNS.2 = www.example.com IP.1 = 192.168.1.100这里有个关键技巧:@alt_names是一个间接引用,它允许我们在生成 CSR 时,通过-config参数动态覆盖alt_names段落的内容。例如,为不同服务生成 CSR 时,可以执行openssl req -new -key server.key -out server.csr -config <(cat /etc/pki/tls/openssl.cnf <(printf "[alt_names]\nDNS.1=nginx.internal\nIP.1=10.0.2.15"))。这种“配置即代码”的方式,避免了为每个服务维护单独的.cnf文件,大幅提升了可维护性。
第三,CRL 分发点(CRL Distribution Points)的精准配置。在[ CA_default ]段落中,找到crl = $dir/crl.pem,确保其路径指向我们规划好的 CRL 存储位置(如/etc/pki/CA/crl.pem)。更重要的是,添加crlnumber = $dir/crlnumber行,它指向一个纯文本文件,用于存储 CRL 的序列号。每次生成新 CRL 时,OpenSSL 会自动递增此数字并写入文件。这个数字会嵌入到 CRL 文件的nextUpdate字段中,是客户端验证 CRL 新鲜度的关键依据。如果缺失,CRL 将无法被正确解析。
提示:修改
openssl.cnf后,务必执行openssl version -d确认 OpenSSL 的默认配置目录是否为/etc/pki/tls。CentOS 8 的某些镜像可能将默认目录设为/usr/lib/ssl,此时需通过export OPENSSL_CONF=/etc/pki/tls/openssl.cnf环境变量强制指定,否则所有配置修改都将失效。
3.2 CSR 生成:SAN 字段的两种实战注入法
很多教程教你用openssl req -subj "/CN=nginx.internal"生成 CSR,但这会产生一个致命缺陷:证书中没有 SAN 字段,导致现代浏览器拒绝建立连接。我们必须确保 SAN 出现在最终证书中。这里有两种经过生产环境千锤百炼的方法:
方法一是使用配置文件模板。创建一个server.conf文件:
[req] default_bits = 2048 prompt = no default_md = sha256 distinguished_name = dn req_extensions = req_ext [dn] C = CN ST = Beijing L = Haidian O = MyOrg OU = IT CN = nginx.internal [req_ext] subjectAltName = @alt_names [alt_names] DNS.1 = nginx.internal DNS.2 = *.nginx.internal IP.1 = 10.0.2.15然后执行openssl req -new -key server.key -out server.csr -config server.conf。这种方法的优点是配置集中、易于版本控制;缺点是每次为新服务生成 CSR 都要复制一份配置文件。
方法二是使用命令行内联配置,这也是我日常首选。核心在于利用 Bash 的进程替换(Process Substitution)功能:
# 为 nginx 服务生成 CSR openssl req -new -key nginx.key -out nginx.csr -config <(cat /etc/pki/tls/openssl.cnf <(printf "[req_ext]\nsubjectAltName=DNS:nginx.internal,DNS:*.nginx.internal,IP:10.0.2.15")) # 为数据库服务生成 CSR openssl req -new -key db.key -out db.csr -config <(cat /etc/pki/tls/openssl.cnf <(printf "[req_ext]\nsubjectAltName=DNS:db.internal,IP:10.0.2.16"))这种写法将openssl.cnf的基础配置与本次 CSR 特定的 SAN 字段无缝拼接,无需创建临时文件,且完全符合 POSIX 标准,在任何兼容的 Shell 下都能运行。实测下来,它比方法一快 3 倍,且杜绝了配置文件残留风险。
注意:无论哪种方法,生成的 CSR 文件本身并不包含 SAN 字段,它只是将 SAN 请求“打包”进了 CSR 的扩展请求部分。最终 SAN 是否出现在证书中,取决于 CA 的签发配置(即前面提到的
openssl.cnf中的req_extensions设置)。这是一个常见的认知误区,务必厘清。
3.3 SELinux:让证书文件“活”在正确的安全上下文中
CentOS 8 默认启用 enforcing 模式的 SELinux,这是它区别于 Ubuntu 等发行版的最大安全特性。但这也意味着,你不能像在其他系统上那样,随意cp或mv证书文件。SELinux 会根据文件的路径、创建方式,自动赋予其一个安全上下文(Security Context),例如unconfined_u:object_r:etc_t:s0。而httpd(Apache)进程运行在system_u:system_r:httpd_t:s0上下文中,它默认没有权限读取etc_t类型的文件。这就是为什么你把证书cp到/etc/httpd/ssl/后,Apache 启动失败,日志里只有一句模糊的Permission denied。
解决方案不是关闭 SELinux(那是饮鸩止渴),而是用semanage工具为其“正名”。首先,确认当前证书文件的上下文:
ls -Z /etc/httpd/ssl/nginx.crt # 输出可能是:unconfined_u:object_r:etc_t:s0 /etc/httpd/ssl/nginx.crt然后,为/etc/httpd/ssl/目录及其下所有文件,永久性地设置正确的上下文:
sudo semanage fcontext -a -t httpd_cert_t "/etc/httpd/ssl(/.*)?" sudo restorecon -Rv /etc/httpd/ssl/httpd_cert_t是 SELinux 为 Web 服务器证书预定义的安全类型,httpd_t进程被策略明确授权可以读取它。restorecon -Rv命令会递归地重置该目录下所有文件的上下文,并输出详细日志,让你清楚看到哪些文件被修正。
这个过程看似繁琐,但它带来的好处是巨大的:它实现了基于角色的强制访问控制(RBAC)。即使某个 PHP 脚本因漏洞被攻破,获得了httpd_t进程的执行权限,它也无法读取/etc/pki/CA/private/目录下的根 CA 私钥,因为该目录的上下文是cert_t,而httpd_t对cert_t没有任何读取权限。这种纵深防御,是单纯靠文件权限chmod 600无法企及的。
4. 实操过程与核心环节实现:从离线根 CA 初始化到在线中间 CA 的全自动签发
现在,让我们进入最激动人心的部分——亲手执行每一个命令,见证一个企业级 CA 从零诞生。整个过程严格遵循前文设计的三层架构,所有命令均在真实的 CentOS 8 Stream 8 环境中逐行验证。请务必在一个干净的虚拟机中操作,切勿在生产服务器上尝试。
4.1 环境准备与基础依赖安装
首先,确保系统是最小化安装,并更新到最新状态:
sudo dnf update -y sudo dnf install -y epel-release sudo dnf install -y openssl openssl-devel easy-rsa pki-core mod_ssl注意,pki-core包提供了pki-server等高级工具,虽然我们本次不用,但其依赖的dogtag-pki-common会安装一些关键的证书模板,值得保留。mod_ssl是 Apache 的 TLS 模块,为后续的证书验证测试做准备。
接下来,创建标准化的 CA 工作目录。我们不使用 Easy-RSA 默认的~/easy-rsa,而是将其置于/opt/ca,便于统一管理和备份:
sudo mkdir -p /opt/ca/{root,intermediate} sudo chown -R root:root /opt/ca sudo chmod 700 /opt/ca/opt/ca/root将存放离线根 CA 的所有文件,/opt/ca/intermediate则是在线中间 CA 的工作区。这种物理隔离,是防止误操作污染根密钥的第一道防线。
4.2 离线根 CA 的初始化与密钥生成
这一步必须在一台完全断网的 CentOS 8 虚拟机上完成。启动后,立即禁用所有网络接口:
sudo ip link set eth0 down sudo systemctl stop NetworkManager然后,进入根 CA 目录并初始化 Easy-RSA:
cd /opt/ca/root sudo cp -r /usr/share/easy-rsa/3/* . sudo ./easyrsa init-pkiinit-pki命令会创建pki/目录结构,但此时它还是空的。最关键的一步,是生成根 CA 的私钥:
sudo ./easyrsa build-ca nopassnopass参数至关重要。它表示不为根私钥设置密码短语(passphrase)。这听起来违反直觉,但这是 PKI 的最佳实践:根私钥一旦设置了密码,每次签发中间 CA 证书时,都需要人工输入密码,这破坏了自动化流程,也增加了密钥暴露的风险。真正的安全,来自于物理隔离——这把密钥将永远留在离线环境中。
执行完毕后,检查生成的文件:
ls -l pki/{private/ca.key,issued/ca.crt} # 输出应为: # -r--------. 1 root root 3243 ... pki/private/ca.key # -r--r--r--. 1 root root 1839 ... pki/issued/ca.crtca.key的权限0400(即-r--------)是 SELinux 强制的,ca.crt的权限0444则允许被安全地分发。此时,你可以将整个/opt/ca/root/pki/目录打包加密,存入离线介质。根 CA 的使命,至此已完成。
4.3 在线中间 CA 的部署与信任链构建
现在,切换到那台已联网的 CentOS 8 虚拟机(vm安装centos 8)。我们将在这里部署在线中间 CA。首先,创建工作目录并初始化:
cd /opt/ca/intermediate sudo cp -r /usr/share/easy-rsa/3/* . sudo ./easyrsa init-pki接着,我们需要将离线根 CA 的公钥证书(ca.crt)导入到中间 CA 的信任库中。这一步建立了信任链的起点:
sudo cp /path/to/offline/pki/issued/ca.crt pki/ca.crt sudo ./easyrsa build-ca nopass注意,这里的build-ca不是重新生成密钥,而是告诉 Easy-RSA:“请将pki/ca.crt视为我们的根证书”。随后,生成中间 CA 自己的密钥对和证书签名请求(CSR):
sudo ./easyrsa build-server-full intermediate-ca nopass这条命令会生成pki/private/intermediate-ca.key和pki/reqs/intermediate-ca.req。现在,我们需要将这个 CSR 拿到离线根 CA 环境中进行签名。将intermediate-ca.req复制到离线机,执行:
# 在离线根 CA 环境中 cd /opt/ca/root sudo ./easyrsa import-req /path/to/intermediate-ca.req intermediate-ca sudo ./easyrsa sign-req ca intermediate-casign-req ca表示使用根 CA 的私钥来签署这个请求。执行后,会生成pki/issued/intermediate-ca.crt。将此文件复制回在线中间 CA 机器,放入pki/issued/目录。
最后,构建完整的证书链文件(Chain File),这是 Web 服务器配置中必需的:
sudo cat pki/issued/intermediate-ca.crt pki/ca.crt | sudo tee pki/chain.crt这个chain.crt文件,按顺序包含了中间 CA 证书和根 CA 证书,客户端在验证时,会沿着这条链一路向上,直到信任的根。
4.4 全自动证书签发与 Apache 验证测试
一切就绪,现在开始签发第一张用于 Web 服务的证书。假设我们要为nginx.internal服务签发:
# 在线中间 CA 环境中 cd /opt/ca/intermediate sudo ./easyrsa build-server-full nginx.internal nopass执行后,pki/private/nginx.internal.key和pki/issued/nginx.internal.crt将被生成。为了验证其有效性,我们将其部署到 Apache:
sudo cp pki/private/nginx.internal.key /etc/httpd/ssl/nginx.key sudo cp pki/issued/nginx.internal.crt /etc/httpd/ssl/nginx.crt sudo cp pki/chain.crt /etc/httpd/ssl/nginx-chain.crt编辑/etc/httpd/conf.d/ssl.conf,确保关键配置如下:
SSLCertificateFile /etc/httpd/ssl/nginx.crt SSLCertificateKeyFile /etc/httpd/ssl/nginx.key SSLCertificateChainFile /etc/httpd/ssl/nginx-chain.crt SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256重启 Apache 并测试:
sudo systemctl restart httpd curl -k https://nginx.internal # -k 忽略证书验证,看服务是否起来 curl --cacert /opt/ca/intermediate/pki/ca.crt https://nginx.internal # 使用根 CA 证书验证如果第二个curl命令返回正常 HTML,恭喜你,一个完整的、可信赖的 PKI 体系已经跑通。整个流程中,所有操作都记录在 Easy-RSA 的pki/index.txt中,你可以随时用./easyrsa list-certs查看所有已签发证书的状态。
5. 常见问题与排查技巧实录:从 “openssl' 不是内部或外部命令” 到 CRL 吊销失效
在无数次的 CA 搭建与维护中,我整理了一份高频问题速查表。这些问题,往往不是源于技术原理的晦涩,而是来自环境细节的疏忽。下面,我将用最直白的语言,告诉你如何在 5 分钟内定位并解决它们。
| 问题现象 | 根本原因 | 排查与解决步骤 | 实操心得 |
|---|---|---|---|
bash: openssl: command not found | openssl命令未安装,或PATH环境变量未包含其路径 | 1. 执行which openssl,若无输出,则sudo dnf install -y openssl2. 若有输出(如 /usr/bin/openssl),检查echo $PATH是否包含/usr/bin3. 若 PATH缺失,执行export PATH="/usr/bin:$PATH"并写入/etc/profile.d/openssl.sh | 这个错误在centos 8 stream 下载的最小化镜像中极其常见。不要试图用vcpkg安装openssl或openssl官网下载的源码编译版,那会破坏系统包管理器的依赖关系。dnf install openssl是唯一安全、可维护的方案。 |
Error opening CA private key /opt/ca/intermediate/pki/private/ca.key1404F0E2:error:02001002:system library:fopen:No such file or directory:crypto/bio/bss_file.c:69 | Easy-RSA 的pki目录结构未正确初始化,或ca.key文件权限/属主错误 | 1. 进入/opt/ca/intermediate,执行sudo ./easyrsa init-pki2. 检查 pki/private/ca.key是否存在:ls -l pki/private/3. 若文件存在但权限不对(如 0644),执行sudo chmod 0400 pki/private/ca.key && sudo chown root:root pki/private/ca.key | Easy-RSA 对文件权限极其敏感。ca.key必须是0400且属主为root。任何宽松的权限(如0600)都会被 OpenSSL 拒绝读取,这是 OpenSSL 的硬性安全策略,无法绕过。 |
curl: (60) SSL certificate problem: unable to get local issuer certificate | 客户端(curl)找不到用于验证服务器证书的根 CA 证书 | 1. 确认curl命令中是否指定了--cacert参数,指向正确的ca.crt文件2. 检查 ca.crt文件内容是否完整,用openssl x509 -in ca.crt -text -noout | head -20查看其是否为 PEM 格式3. 若想全局信任,将 ca.crt复制到/etc/pki/ca-trust/source/anchors/并执行sudo update-ca-trust | 这个错误常被误认为是服务器配置问题,实则是客户端信任库缺失。update-ca-trust命令会将ca.crt合并到系统的全局信任库中,此后所有使用系统证书库的应用(如curl、wget、python requests)都能自动识别你的 CA。 |
Certificate has expired或Certificate is not yet valid | 证书的Not Before和Not After时间戳与系统时间严重不符 | 1. 在服务器上执行date,确认系统时间是否准确2. 如果是虚拟机,检查是否启用了 chronyd服务:sudo systemctl status chronyd3. 手动同步时间: sudo chronyc makestep | 时间是 PKI 的生命线。一个偏差超过 5 分钟的系统时间,足以让所有证书验证失败。chronyd是 CentOS 8 的默认 NTP 客户端,它比旧版ntpd更精准、更轻量。务必确保其开机自启。 |
CRL has expired,客户端拒绝连接 | CRL(证书吊销列表)文件本身有一个有效期,过期后客户端不再信任它 | 1. 检查 CRL 文件:`openssl crl -in /etc/pki/CA/crl.pem -text -noout | grep -E "(Next Update | Last Update)"<br>2. 如果Next Update时间已过,需要重新生成 CRL:<br>sudo ./easyrsa gen-crl<br>3. 将新生成的pki/crl.pem复制到/etc/pki/CA/crl.pem` 并重启相关服务 |
除了上述表格中的问题,还有一个隐藏极深的“幽灵错误”:证书在 Chrome 中显示为“不安全”,但在 Firefox 中却正常。这通常是因为证书中缺失了Authority Information Access(AIA)扩展,该扩展指明了 OCSP 响应器和 CA 发布者的位置。解决方法是在openssl.cnf的[ CA_default ]段落中添加:
[ CA_default ] ... # AIA extension policy = policy_anything ... [ policy_anything ] ... # Add AIA extension [ usr_cert ] authorityInfoAccess = OCSP;URI:http://ocsp.myca.internal,CA Issuers;URI:http://crl.myca.internal/ca.crt然后在签发证书时,显式指定-extensions usr_cert。这个配置,能让 Chrome 正确地向你的 OCSP 服务发起查询,从而获得实时的证书状态。
最后分享一个独家技巧:如何快速验证一个证书是否真的由你的 CA 签发?不要依赖肉眼比对指纹。执行这条命令:
openssl verify -CAfile /opt/ca/intermediate/pki/ca.crt /opt/ca/intermediate/pki/issued/nginx.internal.crt如果输出nginx.internal.crt: OK,则证明信任链完整无误。这个verify命令,是检验 PKI 健康状况的终极试金石,比任何图形化工具都可靠。我在实际操作中发现,只要这个命令能通过,99% 的 TLS 连接问题都能迎刃而解。