本文是对https://www.feistyduck.com/library/openssl-cookbook/online/ch-openssl.html#openssl-private-ca的翻译
创建私有证书颁发机构
如果您想建立自己的 CA,您需要的一切都已包含在 OpenSSL 中。 用户界面纯粹是基于命令行的,因此对用户不是很友好,但这可能会更好。 经历这个过程很有教育意义,因为它迫使你思考每一个方面,甚至是最小的细节。
尽管还有其他原因,设置私有 CA 的教育意义是我建议这样做的主要原因。 基于 OpenSSL 的 CA,尽管它可能很粗糙,但可以很好地满足个人或小团队的需求。 例如,在开发环境中使用私有 CA 比在任何地方都使用自签名证书要好得多。 同样,提供双重身份验证的客户端证书可以显着提高敏感 Web 应用程序的安全性。
运行私有 CA 的最大挑战不是设置所有内容,而是保持基础架构的安全。 例如,根密钥必须保持离线,因为所有安全都依赖于它。 另一方面,CRLs 和 OCSP 响应者证书必须定期刷新,这需要使根联机。
在阅读本节时,您将创建两个配置文件:一个用于控制根 CA (root-ca.conf),另一个用于控制从属 CA (sub-ca.conf)。 虽然您应该可以按照我的说明从头开始做所有事情,但您也可以从我的 GitHub 帐户下载配置文件模板。20后一种方法可以节省您一些时间,但前一种方法可以让您更好地理解所涉及的工作。
特性和限制
在本节的其余部分,我们将创建一个与公共 CA 结构相似的私有 CA。 将有一个根 CA,可以从中创建其他从属 CA。 我们将通过 CRL 和 OCSP 响应程序提供撤销信息。 为了使根 CA 保持离线状态,OCSP 响应者将拥有自己的身份。 这不是您可以拥有的最简单的私有 CA,但它是可以正确保护的。 作为奖励,从属 CA 将在技术上受到限制,这意味着它只能为允许的主机名颁发证书。
设置完成后,必须将根证书安全地分发给所有预期的客户端。 一旦根就位,您就可以开始颁发客户端和服务器证书。 这种设置的主要限制是 OCSP 响应器主要是为测试而设计的,只能用于较轻的负载。
创建根 CA
创建一个新的 CA 涉及几个步骤:配置、创建目录结构和初始化密钥文件,最后生成根密钥和证书。 本节介绍流程以及常见的 CA 操作。
根 CA 配置
在我们实际创建 CA 之前,我们需要准备一个配置文件 (root-ca.conf),它将准确地告诉 OpenSSL 我们希望如何设置。 在正常使用期间,大多数时候不需要配置文件,但在涉及复杂操作(例如创建根 CA)时,它们是必不可少的。 OpenSSL 配置文件功能强大; 在您继续之前,我建议您熟悉它们的功能(命令行上的 man config)。
配置文件的第一部分包含一些基本的 CA 信息,例如名称和基本 URL,以及 CA 专有名称的组成部分。 因为语法很灵活,信息只需要提供一次:
[default]
name = root-ca
domain_suffix = example.com
aia_url = http://$name.$domain_suffix/$name.crt
crl_url = http://$name.$domain_suffix/$name.crl
ocsp_url = http://ocsp.$name.$domain_suffix:9080
default_ca = ca_default
name_opt = utf8,esc_ctrl,multiline,lname,align
[ca_dn]
countryName = "GB"
organizationName = "Example"
commonName = "Root CA"
第二部分直接控制 CA 的操作。 有关每个设置的完整信息,请参阅 ca 命令的文档(命令行上的 man ca)。 大多数设置是不言自明的; 我们主要告诉 OpenSSL 我们要将文件保存在哪里。 因为这个根 CA 将仅用于下级 CA 的颁发,所以我选择让证书有效期为 10 年。 对于签名算法,默认使用安全的 SHA256。
配置默认策略 (policy_c_o_match),以便从该 CA 颁发的所有证书都具有与 CA 匹配的 countryName 和 organizationName 字段。 公共 CA 通常不会这样做,但它适用于私有 CA:
[ca_default]
home = .
database = $home/db/index
serial = $home/db/serial
crlnumber = $home/db/crlnumber
certificate = $home/$name.crt
private_key = $home/private/$name.key
RANDFILE = $home/private/random
new_certs_dir = $home/certs
unique_subject = no
copy_extensions = none
default_days = 3650
default_crl_days = 365
default_md = sha256
policy = policy_c_o_match
[policy_c_o_match]
countryName = match
stateOrProvinceName = optional
organizationName = match
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
第三部分包含 req 命令的配置,该命令仅在创建自签名根证书期间使用一次。 最重要的部分在扩展中:basicConstraints 扩展表明证书是一个 CA,keyUsage 包含此场景的适当设置:
[req]
default_bits = 4096
encrypt_key = yes
default_md = sha256
utf8 = yes
string_mask = utf8only
prompt = no
distinguished_name = ca_dn
req_extensions = ca_ext
[ca_ext]
basicConstraints = critical,CA:true
keyUsage = critical,keyCertSign,cRLSign
subjectKeyIdentifier = hash
配置文件的第四部分包含在构建由根 CA 颁发的证书期间将使用的信息。 如 basicConstraints 扩展所示,所有证书都是 CA,但我们将 pathlen 设置为零,这意味着不允许进一步的从属 CA。
所有从属 CA 都将受到限制,这意味着它们颁发的证书将仅对域名子集和受限用途有效。 首先,extendedKeyUsage 扩展只指定了clientAuth 和serverAuth,即TLS 客户端和服务器认证。 其次,nameConstraints 扩展将允许的主机名仅限于 example.com 和 example.org 域名。 从理论上讲,此设置使您能够将下级 CA 的控制权交给其他人,但仍然可以安全地知道他们不能为任意主机名颁发证书。 如果您愿意,您可以将每个从属 CA 限制在一个小的域名称空间中。 排除这两个 IP 地址范围的要求来自 CA/浏览器论坛的基线要求,其中定义了技术受限的从属 CA。21
在实践中,名称约束并不完全实用,因为一些主要平台目前不识别 nameConstraints 扩展。 如果您将此扩展标记为关键,则此类平台将拒绝您的证书。 如果您不将其标记为关键(如示例中所示),您将不会遇到此类问题,但是其他一些平台不会强制执行它。
[sub_ca_ext]
authorityInfoAccess = @issuer_info
authorityKeyIdentifier = keyid:always
basicConstraints = critical,CA:true,pathlen:0
crlDistributionPoints = @crl_info
extendedKeyUsage = clientAuth,serverAuth
keyUsage = critical,keyCertSign,cRLSign
nameConstraints = @name_constraints
subjectKeyIdentifier = hash
[crl_info]
URI.0 = $crl_url
[issuer_info]
caIssuers;URI.0 = $aia_url
OCSP;URI.0 = $ocsp_url
[name_constraints]
permitted;DNS.0=example.com
permitted;DNS.1=example.org
excluded;IP.0=0.0.0.0/0.0.0.0
excluded;IP.1=0:0:0:0:0:0:0:0/0:0:0:0:0:0:0:0
配置的第五部分也是最后一部分指定要与证书一起用于 OCSP 响应签名的扩展。 为了能够运行 OCSP 响应程序,我们生成一个特殊证书并将 OCSP 签名能力委托给它。 此证书不是 CA,您可以从扩展中看到:
[ocsp_ext]
authorityKeyIdentifier = keyid:always
basicConstraints = critical,CA:false
extendedKeyUsage = OCSPSigning
keyUsage = critical,digitalSignature
subjectKeyIdentifier = hash
根 CA 目录结构
下一步是创建上一节中指定的目录结构,并初始化一些将在 CA 操作期间使用的文件:
$ mkdir root-ca
$ cd root-ca
$ mkdir certs db private
$ chmod 700 private
$ touch db/index
$ openssl rand -hex 16 > db/serial
$ echo 1001 > db/crlnumber
使用以下子目录:
certs/
证书存储; 新证书将在颁发时放置在这里。
db/
此目录用于证书数据库(索引)和保存下一个证书和 CRL 序列号的文件。 OpenSSL 将根据需要创建一些附加文件。
private/
该目录将存储私钥,一个用于 CA,另一个用于 OCSP 响应者。 重要的是没有其他用户可以访问它。 (事实上,如果您要认真对待 CA,存储根材料的机器应该只有最少数量的用户帐户。)
注意
在创建新的 CA 证书时,使用随机数生成器初始化证书序列号很重要,就像我在本节中所做的那样。 如果您最终创建和部署了多个具有相同专有名称的 CA 证书,这将非常有用(如果您犯了错误并需要重新开始,这很常见); 将避免冲突,因为证书将具有不同的序列号。
根 CA 生成
我们采取两个步骤来创建根 CA。 首先,我们生成密钥和 CSR。 当我们使用 - config 开关时,将从配置文件中获取所有必要的信息:
$ openssl req -new \
-config root-ca.conf \
-out root-ca.csr \
-keyout private/root-ca.key
第二步,我们创建一个自签名证书。 -extensions 开关指向配置文件中的 ca_ext 部分,该部分激活适用于根 CA 的扩展:
$ openssl ca -selfsign \
-config root-ca.conf \
-in root-ca.csr \
-out root-ca.crt \
-extensions ca_ext
数据库文件的结构
db/index 中的数据库是一个包含证书信息的明文文件,每行一个证书。 在创建根 CA 之后,它应该只包含一行:
V 240706115345Z 1001 unknown /C=GB/O=Example/CN=Root CA
每行包含由制表符分隔的六个值:
- 状态标志(V 为有效,R 为已撤销,E 为过期)
- 到期日期(YYMMDDHHMMSSZ 格式)
- 撤销日期或未撤销则为空
- 序列号(十六进制)
- 文件位置或未知(如果不知道)
- 专有名称
根 CA 操作
要从新 CA 生成 CRL,请使用 ca 命令的 -gencrl 开关:
$ openssl ca -gencrl \
-config root-ca.conf \
-out root-ca.crl
要颁发证书,请使用所需参数调用 ca 命令。 重要的是-extensions 开关指向配置文件中的正确部分(例如,您不想创建另一个根CA)。
$ openssl ca \
-config root-ca.conf \
-in sub-ca.csr \
-out sub-ca.crt \
-extensions sub_ca_ext
要撤销证书,请使用 ca 命令的 -revoke 开关; 您需要有一份您希望撤销的证书的副本。 因为所有的证书都存放在 certs/ 目录下,所以只需要知道序列号即可。 如果您有专有名称,则可以在数据库中查找序列号。
为 -crl_reason 开关中的值选择正确的原因。 该值可以是以下之一:unspecified、keyCompromise、CACompromise、affiliationChanged、superseded、cessationOfOperation、certificateHold 和 removeFromCRL。
$ openssl ca \
-config root-ca.conf \
-revoke certs/1002.pem \
-crl_reason keyCompromise
为 OCSP 签名创建证书
首先,我们为 OCSP 响应者创建一个密钥和 CSR。 这两个操作与任何非 CA 证书一样完成,这就是我们不指定配置文件的原因:
$ openssl req -new \
-newkey rsa:2048 \
-subj "/C=GB/O=Example/CN=OCSP Root Responder" \
-keyout private/root-ocsp.key \
-out root-ocsp.csr
其次,使用根 CA 颁发证书。 -extensions 开关的值指定 ocsp_ext,它确保设置了适合 OCSP 签名的扩展。 我将新证书的生命周期缩短到 365 天(从默认的 3,650 天)。 因为这些 OCSP 证书不包含吊销信息,所以它们不能被吊销。 因此,您希望尽可能缩短生命周期。 假如您准备以一个频率生成新的证书,30 天是一个不错的选择:
$ openssl ca \
-config root-ca.conf \
-in root-ocsp.csr \
-out root-ocsp.crt \
-extensions ocsp_ext \
-days 30
现在您已准备好启动 OCSP 响应程序。 对于测试,您可以在根 CA 所在的同一台机器上进行。 但是,对于生产环境,您必须将 OCSP 响应者密钥和证书移到其他地方:
$ openssl ocsp \
-port 9080
-index db/index \
-rsigner root-ocsp.crt \
-rkey private/root-ocsp.key \
-CA root-ca.crt \
-text
您可以使用以下命令行测试 OCSP 响应程序的操作:
$ openssl ocsp \
-issuer root-ca.crt \
-CAfile root-ca.crt \
-cert root-ocsp.crt \
-url http://127.0.0.1:9080
在输出中,verify OK 表示签名已正确验证,good 表示证书尚未被吊销。
Response verify OK
root-ocsp.crt: good
This Update: Jul 9 18:45:34 2014 GMT
创建从属 CA
从属 CA 的生成过程很大程度上与根 CA 的过程相同。 在本节中,我将仅在适当的地方强调差异。 有关其他所有内容,请参阅上一节。
从属 CA 配置
要为从属 CA 生成配置文件 (sub-ca.conf),请从我们用于根 CA 的文件开始,并进行本节中列出的更改。 我们将名称更改为 sub-ca 并使用不同的专有名称。 我们会将 OCSP 响应程序放在不同的端口上,但这只是因为 ocsp 命令不支持虚拟主机。 如果您为 OCSP 响应程序使用了适当的 Web 服务器,则可以完全避免使用特殊端口。 新证书的默认有效期为 365 天,我们将每 30 天生成一次新的 CRL。
将 copy_extensions 更改为 copy 意味着来自 CSR 的扩展将被复制到证书中,但前提是它们尚未在我们的配置中设置。 通过此更改,准备 CSR 的任何人都可以将所需的替代名称放入其中,从中提取的信息将被提取并放入证书中。 此功能有些危险(您允许其他人对证书中的内容进行有限的直接控制),但我认为它适用于较小的环境:
[default]
name = sub-ca
ocsp_url = http://ocsp.$name.$domain_suffix:9081
[ca_dn]
countryName = "GB"
organizationName = "Example"
commonName = "Sub CA"
[ca_default]
default_days = 365
default_crl_days = 30
copy_extensions = copy
在配置文件的末尾,我们将添加两个新配置节,分别用于客户端证书和服务器证书。 唯一的区别在于 keyUsage 和 extendedKeyUsage 扩展。 请注意,我们指定了 basicConstraints 扩展,但将其设置为 false。 我们这样做是因为我们从 CSR 复制扩展。 如果我们不考虑这个扩展,我们最终可能会使用 CSR 中指定的扩展:
[server_ext]
authorityInfoAccess = @issuer_info
authorityKeyIdentifier = keyid:always
basicConstraints = critical,CA:false
crlDistributionPoints = @crl_info
extendedKeyUsage = clientAuth,serverAuth
keyUsage = critical,digitalSignature,keyEncipherment
subjectKeyIdentifier = hash
[client_ext]
authorityInfoAccess = @issuer_info
authorityKeyIdentifier = keyid:always
basicConstraints = critical,CA:false
crlDistributionPoints = @crl_info
extendedKeyUsage = clientAuth
keyUsage = critical,digitalSignature
subjectKeyIdentifier = hash
对配置文件满意后,按照与根 CA 相同的过程创建目录结构。 只需使用不同的目录名称,例如 sub-ca。
从属 CA 生成
和根 CA 一样,我们采取两个步骤来创建从属 CA。 首先,我们生成密钥和 CSR。 当我们使用 -config 开关时,所有必要的信息都将从配置文件中获取。
$ openssl req -new \
-config sub-ca.conf \
-out sub-ca.csr \
-keyout private/sub-ca.key
第二步,我们让根 CA 颁发证书。 -extensions 开关指向配置文件中的 sub_ca_ext 部分,该部分激活适用于从属 CA 的扩展。
$ openssl ca \
-config root-ca.conf \
-in sub-ca.csr \
-out sub-ca.crt \
-extensions sub_ca_ext
从属 CA 操作
要颁发服务器证书,请在 -extensions 开关中指定 server_ext 时处理 CSR:
$ openssl ca \
-config sub-ca.conf \
-in server.csr \
-out server.crt \
-extensions server_ext
要颁发客户端证书,请在 -extensions 开关中指定 client_ext 时处理 CSR:
$ openssl ca \
-config sub-ca.conf \
-in client.csr \
-out client.crt \
-extensions client_ext
注意
当请求新证书时,其所有信息将在操作完成之前提供给您进行验证。 您应该始终确保一切都是合宜的,尤其是当您正在使用其他人准备的 CSR 时。 请特别注意证书专有名称以及 basicConstraints 和 subjectAlternativeName 扩展。
CRL 生成和证书吊销与根 CA 相同。 OCSP 响应器唯一不同的是端口。 从属 CA 应改为使用 9081。 建议响应者使用自己的证书,这样可以避免将从属 CA 保留在公共服务器上。
[20] OpenSSL CA configuration templates (Bulletproof TLS and PKI GitHub repository, retrieved 20 October 2021)
[21] Baseline Requirements (The CA/Browser Forum, retrieved 9 July 2014)