零、序章
前情提要:OpenSSL命令行实例
注:本文均以RSA密钥为例,整太多就复杂化了
来自官网的警告提示:
此命令最初用作如何在 CA 中执行操作的示例。它的代码没有生产质量。它本身不应该被用作一个完整的CA,但是有些人至少在内部使用它来达到这个目的。执行此操作时,应特别注意正确保护用于签名证书的私钥。建议将它们保存在安全的硬件存储(如智能卡或HSM)中,并通过合适的引擎或加密提供程序访问它们。
此命令命令实际上是单个用户命令:不对各种文件执行锁定,并且尝试在同一数据库上运行多个 openssl ca 命令可能会产生不可预知的结果。
注:正常情况下,我一般只会使用OpenSSL来进行不需要安全保障的一些测试和测试数据生成,不会用于生产实际。需要安全保障的情况下,一般也不会使用简单的命令行工具,而是使用真正的安全设备(如加密机)来保障安全。
额外说明:
本文编写基于的环境:OpenSSL 3.0
本文内容二次验证基于的环境:OpenSSL 1.1.1h
一、创建一些文件夹
命名最好用英文
结果如图
我创建的主文件夹(CA工作目录)名称:myCA
CA文件夹:个人喜好,用于单独存放根CA的证书、csr、私钥
certs文件夹:已签发证书的保存位置,主要是自己归类。( openssl 似乎不会对这个文件夹进行操作)
newcerts文件夹:新签发证书的保存位置,必定输出到该位置。即便你指定了输出路径,这个文件夹还是会生成一份证书文件(此时输出了两本一样的证书)。
index.txt文件:数据库索引文件,即会记录CA颁发的证书的信息
serial文件:颁发出来的证书的 serial number (序列号)。创建时需要往里面输入初始值“01”(也可以填入其他十六进制值表示)(印象中这个序列号长度是有上限的,所以不要随便弄一个很长的数值进来),则颁发证书时会依据这个值开始自动递增。
serial文件初始内容如图
二、复制一份配置文件到主文件夹内
我们要复制的是 openssl.cnf 文件,通常是在 OpenSSL 安装目录的 bin/cnf文件夹 里面
注意:复制的 openssl.cnf 是放置在我们刚刚创建的主文件夹里。
三、编辑openssl.cnf文件
注意,是刚刚复制过来的这个文件,本文修改的都是这个文件
修改图中所示的 [CA_default] 里的内容
dir:主文件夹路径,我这里用“点”表示“当前文件夹”,因为正常都是进入到主文件夹里去执行操作。当然,你这里也可以写刚刚创建的主文件夹的绝对路径,如:F:/myCA。
certs、database、new_certs_dir、serial:都是和刚刚创建的文件/文件夹的路径对应
注意:$dir/certs 之类的表示,意思是查找的是 dir路径下 的 certs文件或文件夹
四、生成CA证书
4.1 生成密钥对
执行命令:openssl genrsa -out ca.key 2048
详见“前情提要”内容
注:我这边把ca.key放入CA文件夹中进行了归类,实际看自己喜好
修改openssl.cnf里关于CA私钥路径的配置如下图:
注意:这里路径以你实际的为准即可,不绝对。
4.2 生成CSR
执行命令:openssl req -new -key ca.key -out ca.csr
详见“前情提要”内容
4.3 生成自签名证书CA 途径一
方式一:使用X509命令生成V1自签名证书
执行命令:openssl x509 -req -days 3650 -sha256 -in ca.csr -signkey ca.key -out ca.crt
方式二:使用X509命令生成V3自签名证书(推荐)
执行命令:openssl x509 -req -days 3650 -sha256 -extfile openssl.cnf -extensions v3_ca -in test.csr -signkey test.key -out test.crt
详见“前情提要”内容
4.4 生成自签名证书CA 途径二
使用本方式前,请先看“七、签发子证书”开头关于 policy_match 的相关描述,避免根证书出问题。
4.4.1 修改openssl.cnf配置
修改后的配置文件内容
解释:原配置是 usr_cert,使得颁发出来的证书的扩展字段用的是末端证书的配置。而如果要颁发CA证书,则应该使用CA证书的配置,所以我这里是将其配置成V3版本的CA证书配置。否则等下颁发出来的自签名CA证书,用的是V3的版本,却缺少必要的扩展字段,在证书链校验时会失败。
注意:颁发CA时,临时使用 v3_ca 的配置;颁发末端证书时,要记得改回成 usr_cert。
有效期配置:原文件默认有效时间长度是365天,可以将其修改成你希望的有效时间长度,如:3650天。
4.4.2 自签名颁证
执行命令:openssl ca -selfsign -in ./CA/ca.csr -config openssl.cnf -out ./CA/ca.crt -keyfile ./CA/ca.key
示例是在主文件夹目录底下运行的
注意1:此处使用 “-config openssl.cnf” 指定使用刚刚我们修改过的配置文件
注意2:此处使用 “-out ./CA/ca.crt” 指定结果文件的额外输出位置。不指定,就只是会输出到刚刚 openssl.cnf 里配置的 new_certs_dir 对应路径中,指定后,new_certs_dir对应路径依旧会有一本以序列号命名的证书文件。
注意3:此处使用 “-keyfile ./CA/ca.key” 指定本次签发证书使用的私钥,如果不指定,那么就会使用配置文件里配置的私钥,即:
默认的签发证书使用的私钥
注意4:每次证书签发过程会有两次提示,第一次是让你确认以下是否真的要签发证书,第二次是让你确认是否要向数据库(就是那个 index.txt 文件)中添加本次颁发证书的信息。
五、第一次签发完证书
你会发现,出现了三个新的文件
index.txt.attr:签发证书时的属性
index.txt.old:上一版本的 index.txt 文件内容
serial.old: 上一版本的 serial 文件内容
5.1 index.txt
当前index.txt中的内容
内容解析:
[证书状态] [证书生效时间] [证书到期时间] [证书吊销时间] [证书序列号] [证书存放路径] [特征名称(DN)值]
证书状态可选值:
V(Vaild),有效
R(Rovoked),吊销
E(Expire),过期
证书生效时间:UTC时间(网友说有,但是该字段我在 1.1.1h 和 3.0 版本没见过)
证书到期时间:UTC时间,如图是:23年9月15日14:02:15过期
证书吊销时间:UTC时间,在证书吊销后出现。如果有设置吊销原因,则后面会跟着显示吊销原因
证书序列号:十六进制格式(依据 serial 文件得来)
证书存放路径:生成证书存储的具体位置,若标记为 unknown(ca指令指定了证书输出文件)不影响证书库的正常使用
特征名称(DN)值:证书主题名(Subject)
对于过期的证书,文本数据库并不会自动更新,需要使用ca指令的 -updatedb 选项进行更新,一般来说,在生成 CRL 之前,都应该使用 -updatedb 进行更新
时间都是零时区时间
5.2 index.txt.attr
当前index.txt.attr中的内容
该文件内容为第一次颁证时自动生成,生成的依据来源是配置的 openssl.cnf(默认是yes)
openssl.cnf的配置
当为 yes 时,严格要求所有签发的 csr 的 subject 不重复,最直观的就是同一本 csr 不能重复签发
报错举例
当为 no 时,允许签发的 csr 的 subject 重复,最直观的就是同一本 csr 可以重复签发
重复签名举例:对ca.csr重复签名
如果要调整配置,直接在 index.txt.attr 中修改 yes/no 即可。(如果一开始就准备允许 subject 重复,则可以在第一次颁证前就在 openssl.cnf 里将该属性设置为 no )
官方文档描述:
如果给定值 yes,则数据库中的有效证书条目必须具有唯一的使用者。如果给出值 no,则几个有效的证书条目可能具有完全相同的主题。默认值为 yes,以便与较旧(0.9.8 之前)版本的 OpenSSL 兼容。但是,为了使 CA 证书滚动更新更容易,建议使用值 no,尤其是在与 -selfsign 命令行选项结合使用时。
请注意,在某些情况下,创建没有任何主题的证书是有效的。如果有多个证书没有主题,则不算作重复。
注:当第二次颁发证书时,又会多出一个文件。这个文件即颁发证书前的 index.txt.attr 的文件内容备份。
5.3 serial
当前serial中的内容
序列号从我们一开始填入的初始值01,变成了02
六、配置CA证书路径
将刚刚生成的自签名的CA证书存在路径配置进 openssl.cnf 中,这样,当我们没有手动指定使用哪本 CA 时,openssl 就会默认使用这个路径对应的 CA证书 来签发子证书。
注:路径要依据实际情况配置
七、签发子证书
注:请颁发子证书前,先查看 openssl.cnf 的配置是否正确。通常情况下,扩展应该配置成 usr_cert。
下图为默认的 OpenSSL 对与 CSR 的 Subject 信息要求:
openssl.cnf原始配置
注:默认情况下,配置要求根证书CA和要颁发的子证书的CSR的国家、城市、组织相同,不然不允许颁发子证书。但是实际两者不一定会相同。所以,可以依据实际情况进行匹配策略的设置。
其中:
match: 该变量在证书请求中的值必须和 CA 的 subject 中的对应项完全相同,否则拒签。
supplied: 该变量在证书请求中必须提供(值可以不同),否则拒签。
optional: 该变量在证书请求中可以存在也可以不存在(相当于没有要求)。
除非 preserve=yes 或者在ca命令中使用了 -preserveDN ,否则在签发证书时将删除匹配策略中未提及的对象。
注意:不知道为什么,默认的 policy_match 里没有配置 localityName 这个字段,这会导致即便csr里有这个字段,也会因为策略未提及而忽略,这很不正常,所以,一定要先补充这个字段
拒签报错举例
修改后的配置
7.1 颁发子CA
执行命令:openssl ca -in test.csr -extensions v3_ca -config openssl.cnf
注:请自行生成test.csr,然后在开头创建的主文件夹内执行命令,并指定csr的路径
注:其中,-extensions v3_ca 的作用同刚刚的在 openssl.cnf 中对扩展字段的配置,即指定扩展字段要用哪个配置。如果是颁发末端证书,则可以使用 -extensions usr_cert 。默认情况下,csr 里附带的所有扩展字段都将被忽略,生成的证书的扩展字段全是配置提供。
操作举例
7.2 颁发末端证书
执行命令:openssl ca -in test.csr -config openssl.cnf
注:请自行生成 test.csr,然后在开头创建的主文件夹内执行命令,并指定 csr的路径
操作举例
注:默认情况下,颁发出来的这本末端证书的扩展字段,用的是 usr_cert 里的配置,原csr中的扩展字段全部被忽略了。
openssl.cnf的usr_cert的配置
7.3 指定CA签发末端证书
执行命令:openssl ca -in test.csr -cert subCA.crt -keyfile subCA.key -config openssl.cnf
不指定的话,就是使用前面我们在 openssl.cnf 里配置的默认的CA和私钥进行证书颁发。
7.4 批量签发证书
执行命令:openssl ca -config openssl.cnf -infiles testa.csr testb.csr testc.csr
执行命令过程中,会一一列举出证书细节让你确认是否要签发证书。
(内容过长,这里就不截图举例了,本质上和普通的证书签发没啥区别)
注意:-infiles 必须放在最后,所有后续参数都被视为包含证书请求的文件的名称。
另外建议:可以创建一个叫 csrs 的文件夹,专门用来放待签发的或者签发过的csr文件。
7.5 签发证书时手动设置证书有效期
执行命令:openssl ca -in testd.csr -startdate 220917111120Z -enddate 20220918114020Z -config openssl.cnf
使用命令:
-startdate: 设置证书有效起始时间(实测时间比当前早也行)。我这里设置的是2022年9月17日11点11分20秒。
-enddate: 设置证书有效结束时间。我这里这里设置的是2022年9月18日11点40分20秒。
输入事件的格式有两种,都支持:
YYMMDDHHMMSSZ (the same as an ASN1 UTCTime structure)
YYYYMMDDHHMMSSZ (the same as an ASN1 GeneralizedTime structure)
最后的 “Z” 必须附带。
注意:这里设置的时间,是零时区的时间。如果你要生成马上过期的证书,则要注意进行时区的转换。
八、让CSR中的扩展字段起效
来自官网的警告提示:
应谨慎使用 copy_extensions 选项。如果不小心,则可能存在安全风险。例如,如果证书请求包含具有 CA:TRUE 的基本约束扩展,并且 copy_extensions 值设置为 copyall,并且用户在显示证书时未发现此扩展,则这将向请求者提供有效的 CA 证书。通过将 copy_extensions 设置为要复制并在配置文件中包含 CA:FALSE 的基本约束,可以避免这种情况。然后,如果请求包含基本约束扩展,则将被忽略。
建议还包括其他扩展(如keyUsage)的值,以防止请求提供自己的值。
可以对 CA 证书本身添加其他限制。例如:如果 CA 证书具有:basicConstraints = CA:TRUE, pathlen:0,那么即使证书是用 CA:TRUE 颁发的,它也是无效的。
开启 copy_extensions(默认是注释掉)
字段解释:是否将证书请求中的扩展项信息加入到证书扩展项中去。
取值范围以及解释:
none: 忽略所有证书请求中的扩展项 (默认)
copy: 将证书扩展项中没有的项目复制到证书中
copyall: 将所有证书请求中的扩展项都复制过去,并且覆盖证书扩展项中原来已经存在的值。
在 copy_extensions = copy 的前提下
usr_cert 模块的配置修改为以下样子:
当前openssl.cnf中的配置
再生成一本带有扩展字段的CSR
注意图中的扩展字段
使用上面介绍的命令,颁发末端证书
执行命令:openssl ca -in test.csr -config openssl.cnf
颁发证书
可见,当使用 copy_extensions = copy 时,ca颁证使用的配置模块若有相应字段的描述,则会直接覆盖csr中的对应字段;若没有相应字段描述,则会使用csr中对应字段内容放入到最终的证书中。
九、创建crlnumber文件
先新建文件夹 “crl”,然后创建 “crlnumber” 文件,往crlnumber里预置初始值 “01”(或者是其他你喜欢的起始数字,这个数字将会作为被吊销的证书记录的编号,更新吊销证书列表时会自增)。
修改 openssl.cnf 配置文件,使得路径对应上
十、生成/更新吊销证书列表(CRL)
10.1 更新文本数据库
还记得上文中生成第一本证书后的介绍说明文字吗?有一处有提及,因为 index.txt 不会自动将过期证书设置为过期,所以在 生成/更新crl 前,需要先 updatedb,更新数据库索引以清除过期的证书。
执行命令:openssl ca -updatedb -config openssl.cnf
没有证书过期时的示例
当真的有证书过期时,会多出现一行,提示哪个编号的证书过期了。
有证书过期时的示例
与此同时,index.txt 中对应行也发生了变化,原本 valid 状态的证书变成了 expire 状态
index.txt文件
10.2 生成/更新CRL
执行命令:openssl ca -gencrl -out ./crl/crl.pem -config openssl.cnf
示例
注:我发现,即便我还没有开始吊销任何一本证书,但是我执行了这个命令,crlnumber 就会产生 crlnumber.old 文件,然后 crlnumber 里的编号就会开始自增(但是是虚的,等吊销证书真有变化后再生成,就恢复正常了)。
注:这里必须写 “-out crl.pem”,不然的话实测没输出内容
十一、吊销证书
注:吊销完证书,需要更新CRL,crl.pem 里的数据才会是新的数据。
11.1 普通吊销(不标注原因)
执行命令:openssl ca -revoke ./newcerts/03.pem -config openssl.cnf
示例,吊销03.pem证书
执行成功后,会提示你刚刚吊销的证书的序列号。
index.txt里的对应行变化
注:有没有注意到,相当于上文中颁发完证书生成的字段,开头的 “V” 变成了 “R”,然后多了一个时间字段,这个时间,即为“证书吊销的时间”,但是看样子是零时区的时间。
11.2 标注原因的吊销
执行命令:openssl ca -revoke ./newcerts/04.pem -crl_reason cessationOfOperation -config openssl.cnf
执行命令:openssl ca -revoke ./newcerts/05.pem -crl_reason superseded -config openssl.cnf
可以看到,相对于不标注原因的写法,index.txt 里在吊销证书的时间后面会附带上我们指定的吊销原因。
index.txt里的对应行变化
-crl_reason可选项:
unspecified
keyCompromise
CACompromise
affiliationChanged
superseded
cessationOfOperation
certificateHold
removeFromCRL
说明:
原因的匹配不区分大小写。设置任何吊销原因都会使 CRL v2。
在实践中,“removeFromCRL” 并不是特别有用,因为它仅用于当前未实现的增量 CRL。
十二、 查看crl文件内容
执行命令:openssl crl -in ./crl/crl.pem -noout -text
没有吊销证书时的内容
可见,内容中有CA信息,有被吊销的证书信息(证书序列号,吊销时间,吊销原因)
有吊销证书时的内容
附加
OpenSSL官网:openssl-ca
他人文章指路:openssl ca(签署和自建CA)
一些补充:
上文中很多地方都有出现的配置 [CA_default] 的内容,实际上都可以像设置有效期一样,在操作的时候使用命令进行手动指定。这些内容可以参考上面提供的两个链接,篇幅有限,这边不过多举例说明。