我想动态创建具有任意开始日期和结束日期的自签名证书,包括过去的。我更喜欢使用标准工具,例如 OpenSSL,但任何能完成工作的工具都很棒。
堆栈溢出问题如何生成有效期小于一天的openssl证书?问类似的问题,但我希望我的证书是自签名的。
如果您想知道,自动化测试需要证书。
答案1
过去有两种创建证书的方法。伪造时间 (1)(2),或在签署证书时定义时间间隔 (3)。
1)首先,关于伪造时间:要让一个程序认为它的日期与系统不同,请查看libfaketime
和faketime
要在 Debian 中安装它:
sudo apt-get install faketime
然后您可以在命令faketime
之前使用openssl
。
使用示例:
$faketime 'last friday 5 pm' /bin/date
Fri Apr 14 17:00:00 WEST 2017
$faketime '2008-12-24 08:15:42' /bin/date
Wed Dec 24 08:15:42 WET 2008
从man faketime
:
给定的命令将被欺骗以相信当前系统时间是时间戳中指定的时间。除非另有说明,否则挂钟将从该日期和时间继续运行(请参阅高级选项)。实际上,faketime 是 libfaketime 的一个简单包装,它使用 LD_PRELOAD 机制加载一个小库,该库拦截对 time(2) 和 fstat(2) 等函数的系统调用。
例如,在您的情况下,您可以很好地定义 2008 年的日期,然后创建一个有效期为 2 年至 2010 年的证书。
faketime '2008-12-24 08:15:42' openssl ...
附带说明一下,这个实用程序可以在多个 Unix 版本中使用,包括 MacOS,作为任何类型程序的包装器(不限于命令行)。
需要澄清的是,只有使用此方法加载的二进制文件(及其子文件)的时间才会更改,并且伪造时间不会影响系统其余部分的当前时间。
2)正如@Wyzard所说,您还拥有datefudge
与使用非常相似的包faketime
。
由于差异,datefudge
不影响fstat
(即不改变文件创建时间)。它还有自己的库 datefudge.so,它使用 LD_PRELOAD 加载。
它还具有一个 -s
static time
始终返回所引用的时间的位置,无论已经过去了多少秒。
$ datefudge --static "2007-04-01 10:23" sh -c "sleep 3; date -R"
Sun, 01 Apr 2007 10:23:00 +0100
3)除了伪造时间之外,更简单的是,还可以定义证书有效期的起点和终点签名OpenSSL 中的证书。
您在问题中链接到的问题的误解是,证书有效性不是在请求时(在 CSR 请求时)定义的,而是在签名时定义的。
使用创建自签名证书时openssl ca
,添加选项-startdate
和-enddate
。
根据 openssl 来源,这两个选项中的日期格式openssl/crypto/x509/x509_vfy.c
是 ASN1_TIME 又名 ASN1UTCTime:格式必须是 YYMMDDHHMMSSZ 或 YYYYMMDDHHMMSSZ。
引用openssl/crypto/x509/x509_vfy.c
:
int X509_cmp_time(const ASN1_TIME *ctm, time_t *cmp_time) { static const size_t utctime_length = sizeof("YYMMDDHHMMSSZ") - 1; static const size_t generalizedtime_length = sizeof("YYYYMMDDHHMMSSZ") - 1; ASN1_TIME *asn1_cmp_time = NULL; int i, day, sec, ret = 0; /* * Note that ASN.1 allows much more slack in the time format than RFC5280. * In RFC5280, the representation is fixed: * UTCTime: YYMMDDHHMMSSZ * GeneralizedTime: YYYYMMDDHHMMSSZ * * We do NOT currently enforce the following RFC 5280 requirement: * "CAs conforming to this profile MUST always encode certificate * validity dates through the year 2049 as UTCTime; certificate validity * dates in 2050 or later MUST be encoded as GeneralizedTime." */
来自变更日志(2038 bug?) - 此变更日志只是一个附加脚注,因为它只涉及那些直接使用 API 的人。
1.1.0e 和 1.1.1 之间的更改 [xx XXX xxxx]
*) 添加 ASN.1 类型 INT32、UINT32、INT64、UINT64 以及以 Z 为前缀的变体。这些旨在替换 LONG 和 ZLONG 并确保大小安全。不鼓励使用 LONG 和 ZLONG,并计划在 OpenSSL 1.2.0 中弃用。
因此,创建从 2008 年 1 月 1 日到 2010 年 1 月 1 日的证书可以如下完成:
openssl ca -config /path/to/myca.conf -in req.csr -out ourdomain.pem \
-startdate 200801010000Z -enddate 201001010000Z
或者
openssl ca -config /path/to/myca.conf -in req.csr -out ourdomain.pem \
-startdate 0801010000Z -enddate 1001010000Z
-startdate
并且-enddate
确实出现在openssl
来源和更改日志中;正如 @guntbert 指出的,虽然它们不会出现在主页中man openssl
,但它们也会出现在man ca
:
-startdate date this allows the start date to be explicitly set. The format of the date is YYMMDDHHMMSSZ (the same as an ASN1 UTCTime structure). -enddate date this allows the expiry date to be explicitly set. The format of the date is YYMMDDHHMMSSZ (the same as an ASN1 UTCTime structure).
引用openssl/CHANGE
:
0.9.3a 和 0.9.4 之间的更改 [1999 年 8 月 9 日]
*)修复“ca”程序的 -startdate 和 -enddate (缺少)参数。
PS至于所选择的答案问题您从 StackExchange 引用:它通常是馊主意更改系统时间,尤其是在生产系统中;通过此答案中的方法,您在使用它们时不需要 root 权限。
答案2
我几乎惊讶地发现显而易见的事情有效:虽然openssl
将证书有效的天数作为参数,但只需提供一个负数!
openssl req -x509 -newkey rsa:4096 \
-keyout key.pem -out cert.pem -days -365
请注意,这实际上会导致一些非常奇怪的事情:一个证书的到期时间戳先于它的开始有效时间戳。我实际上不建议您将其用于自动化测试,因为它很奇怪。您可能还需要一种回溯有效期开始时间戳的方法。
答案3
或者你可以使用类似这个简短的 python 程序的东西......(注意事项适用)
它创建一个密钥(test.key)和一个证书(test.crt),其起始时间为过去10年(-10*365*24*60*60秒为-10年),过期时间为过去5年(-5*365*24*60*60)。
请注意,这是一个最小的演示程序,因此它不需要设置任何扩展(例如 basicConstraints)并使用固定串行。
#!/usr/bin/env python
from OpenSSL import crypto
key = crypto.PKey()
key.generate_key(crypto.TYPE_RSA, 2048)
cert = crypto.X509()
cert.get_subject().CN = "Test"
cert.set_serial_number(666)
cert.gmtime_adj_notBefore(-10*365*24*60*60)
cert.gmtime_adj_notAfter(-5*365*24*60*60)
cert.set_issuer(cert.get_subject())
cert.set_pubkey(key)
cert.sign(key, 'sha384')
open("test.crt", "wb").write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
open("test.key", "wb").write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key))