在为我的宠物收养平台设置服务器时,我必须配置 TLS 并使用 certbot 获取 Let's Encrypt 证书。虽然这很容易做到,但 Let's Encrypt 证书的一件“烦人”的事情是它们的有效期只有 90 天,而且我设置服务器的方式我找不到自动续订证书的替代方法,所以我不得不登录服务器并每 90 天手动执行一次该过程。
我心想,如果证书持续超过 90 天就好了,并想知道证书可以持续多长时间,证书中的字段可以持续多长时间?在这篇博客中,我将尝试探索、测试和了解有关 TLS 证书的更多信息。
关于 TLS 证书#
首先要知道证书的格式由标准 X.509(公钥证书)定义,这一点很重要,这就是为什么有时阅读证书时,您会将它们读作“X.509 证书”。尽管根据我的理解,X.509 只定义了公钥证书结构,而 RFC 5280 定义了它们的结构和在 Internet 上对 TLS 的使用。我阅读并遵循了 RFC 5280 的内容。
如上图所示,TLS 证书有几个字段,其中一些字段描述有效性、用于公钥的算法,但还有一些字段只是“信息”字段,例如颁发者名称,可以在创建证书时进行自定义。还有其他选项卡,例如“ISRG Root X1”和“R11”,它们是根证书和中间证书。“ISGR Root X1”颁发了一个名为“R11”的证书,使用“R11”,他们向我的网站颁发了证书“0x00.cl”这是一个信任链,如果您的浏览器或客户端可以信任根证书,例如“ISGR Root X1”,那么它也应该信任它颁发的证书。Chrome 和 Firefox 等浏览器使用通用 CA 数据库 (ccadb) 在其浏览器中包含证书颁发机构 (CA)。
您可以创建自己的根证书并成为自己的 CA,但由于证书不会包含在任何软件中,因此大多数(如果不是全部)软件都会引发错误或至少发出有关证书不受信任的警告。Badssl 是一个显示不良证书和良好证书示例的网站。
创建证书#
我们需要做的第一件事是创建一个证书,这将是根证书。
我在使用 openssl (OpenSSL 3.2.1) 之前已经创建了证书,当您查找如何创建证书时,网站通常会使用 openssl,因为它是一个易于使用的命令行工具,并且与大多数 linux 发行版一起安装。首先,您必须创建一个密钥:
$ openssl genrsa -out myCA.key 2048
那么,争论点是什么呢?它是密钥的大小(以位为单位),特别是因为该子命令生成一个 RSA 私有密钥。此子命令是使用子命令 生成私钥的 “捷径” 。等效项为:2048
genrsa
genpkey
$ openssl genpkey -algorithm RSA -out myCA.key -pkeyopt bits:2048
获得密钥后,可以使用以下子命令创建证书:req
$ openssl req -x509 -new -noenc -key myCA.key -sha256 -days 3650 -out myCA.pem
这将创建一个有效期为 10 年(3650 天)的证书。最后两个文件:
$ ls -l
-rw-------. 1 tomas tomas 1704 Jul 3 12:00 myCA.key
-rw-r--r--. 1 tomas tomas 1237 Jul 3 12:00 myCA.pem
密钥大小#
因为创建密钥是创建证书的第一步,所以我想,如果我将位数设置为更大的数字,而不是使用 2048 使用 65536 怎么办
$ openssl genrsa -out myCA.key 65536
Warning: It is not recommended to use more than 16384 bit for RSA keys.
Your key size is 65536! Larger key size may behave not as expected.
我让它运行了大约 30 分钟,但决定停下来,因为它花费的时间太长,而且看起来不会很快结束。相比之下,2048 位需要 ~250 毫秒,而且 openssl 抛出警告看起来没有希望,所以我选择了 16384(花了 30 秒)。
$ ls -l
-rw-------. 1 tomas tomas 12632 Jul 3 12:00 myCA.key
嗯,密钥大约有 12kB,但就像我之前提到的,子命令专门生成一个 RSA 密钥,但是创建密钥的其他算法呢?他们会输出更大的密钥吗?因此,我首先检查了哪些选项可用(RSA、RSA-PSS、EC、X25519、X448、ED25519 和 ED448)并对其进行了测试,但 RSA 是我唯一可以输出更大字节密钥的选项,还将哈希算法从 SHA256 更改为 SHA3-512,因为它应该输出更多位。genrsa
同样,我使用这个新的私钥创建证书并得到:
$ ls -l
-rw-------. 1 tomas tomas 12632 Jul 3 12:00 myCA.key
-rw-r--r--. 1 tomas tomas 6091 Jul 3 12:00 myCA.pem
太好了,现在证书从 1.2kB 变成了 6kB,只是因为私钥是使用更多位创建的 (16384)。
证书字段#
证书 具有多个字段,这些字段不仅提供有关证书的信息,还提供有关证书的颁发者和使用者的信息。RFC 5280 第 4.1.2.4 节定义了必须存在的 issuer 字段的属性。
- 国家
- 组织名称
- 部门名称
- 可分辨名称限定符
- 州或省名称
- 通用名称(例如,“Susan Housley”)
- 序号。
到目前为止,当我们创建证书时,所有这些字段都保留了默认值,因此这是另一个通过添加长字符串来使证书更大的机会。
$ openssl req -x509 -new -noenc -key myCA.key -sha3-512 -days 3650 -out myCA.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:CC
State or Province Name (full name) []:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Error making certificate request
800275933F7F0000:error:06800097:asn1 encoding routines:ASN1_mbstring_ncopy:string too long:crypto/asn1/a_mbstr.c:106:maxsize=128
好的,所以我不能只添加超长的字符串,在这种情况下,“州或省名称”是有限制的,最大长度为 128......但是其他领域呢?好吧,遗憾的是,RFC 5280 附录 A.1 中定义了上限(openssl 在 crypto/asn1/tbl_standard.h 中定义)。
属性 | 上界 |
---|---|
国家/地区名称 alpha | 2 |
组织名称 | 64 |
部门名称 | 64 |
州或省名称 | 128 |
常用名 | 64 |
地区名称 | 128 |
可以使用标志来传递这些值,而不是像我第一次创建它时那样以交互方式进行传递。-subj
$ openssl req -x509 -new -noenc \
-key myCA.key \
-sha3-512 \
-days 3650 \
-out myCA.pem \
-subj "/C=XX/ST=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/L=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/O=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/OU=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/CN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/emailAddress=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
$ ls -l
-rw-------. 1 tomas tomas 12632 Jul 3 12:00 myCA.key
-rw-r--r--. 1 tomas tomas 7550 Jul 3 12:00 myCA.pem
好吧,我设法将其从 6kB 增加到 7.5kB 左右,不是很多。我将尝试“作弊”并检查我是否可以重复一些属性,我将使用与上述相同的命令,但输入的字符串将被复制粘贴多次。我不会在这里,因为它太长且没有必要,但你明白了。-subj
$ ls -l
-rw-------. 1 tomas tomas 12632 Jul 3 12:00 myCA.key
-rw-r--r--. 1 tomas tomas 249845 Jul 3 12:00 myCA.pem
哇!证书几乎 250kB。现在我们知道了让它变得像我们想要的那样大的 “秘密”,我们可以继续讨论证书的有效期。
有效期#
当我第一次使用 openssl 创建证书时,您可以定义证书应持续多长时间(以天为单位),默认情况下,该字段设置为当前日期,并从当前日期开始根据给定的天数计算日期。notBefore
notAfter
我想测试 .所以我尝试的第一件事是将天数设置为一个大数字。notAfter
$ openssl req -x509 -new -noenc \
-key myCA.key \
-sha3-512 \
-days 36500000000 \
-out myCA.pem
req: Value "36500000000" outside integer range
req: Use -help for summary.
嗯,我想设置一个有效期为 1 亿年的证书太过分了。我将使用一个较小的数字,例如 3650000(10000 年)。
$ openssl req -x509 -new -nodes \
-key myCA.key \
-sha3-512 \
-days 3650000 \
-out myCA.pem
看起来它奏效了!我将检查证书的信息
$ openssl x509 -noout -text -in myCA.pem
Could not open file or uri for loading certificate from myCA.pem: No such file or directory
$ ls -l myCA.pem
ls: cannot access 'myCA.pem': No such file or directory
嗯。。。OpenSSL 抱怨数字太大,当它对 OpenSSL 来说足够小时,它不会在不引发任何错误或警告的情况下创建证书。我测试了一下,通过使用较少的天数来确保命令没有问题,并且它确实创建了证书,因此天数是问题所在。
当我阅读 RFC 5280 第 4.1.2.5 节时,证书接受两种时间格式,“YYMMDDHHMMSSZ”和“YYYYMMDDHHMMSSZ”。RFC 5280 中隐含地写着日期可以从 1950 年到 9999 年,所以这将是我们的限制。
现在我可以通过设置正确的天数在技术上设置为 9999,那么呢?是否可以为证书设置过去的值?openssl 允许吗?notAfter
notBefore
or 命令不允许为证书明确设置日期,但允许签署证书并自定义开始日期和结束日期字段。在尝试生成一个命令一段时间后,我发现可以使用该命令的唯一方法是您必须已经拥有带有证书签名请求 (CSR) 的 CA 证书openssl req
openssl x509
openssl ca
首先,我必须创建一个 CSR:
$ openssl x509 -in myCA.pem -signkey myCA.key -x509toreq -out myCA.csr
因此,我可以创建自定义证书:
$ openssl ca -startdate '19500101000000Z' -enddate '99991231235959Z' -in myCA.csr -keyfile myCA.key -cert myCA.pem -out myCA.crt
Using configuration from /etc/pki/tls/openssl.cnf
Check that the request matches the signature
Signature ok
Certificate Details:
Serial Number:
01:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
Validity
Not Before: Jan 1 00:00:00 1950 GMT
Not After : Dec 31 23:59:59 9999 GMT
Subject:
countryName = XX
stateOrProvinceName = Mandatory
organizationName = Default Company Ltd
commonName = 0x00.cl
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
X509v3 Subject Key Identifier:
7E:F9:D5:33:0E:B8:64:DF:19:94:AB:59:23:A0:0A:55:D0:40:C3:B0
X509v3 Authority Key Identifier:
7E:F9:D5:33:0E:B8:64:DF:19:94:AB:59:23:A0:0A:55:D0:40:C3:B0
Certificate is to be certified until Dec 31 23:59:59 9999 GMT (2912992 days)
Sign the certificate? [y/n]:y
证书有最小和最大日期,很好,但有一件事困扰我,那就是我使用此命令创建新证书,而 CA 证书仍然设置了旧日期。我可以继续尝试使用 openssl 创建这样的证书,也许还可以对 CA 进行自签名,但这对我来说已经变得太复杂了。我不仅必须找出使用命令选项和标志的正确方法,而且在使用命令设置自定义日期时,我必须使用目录和文件设置 openssl.conf。因此,我转向 Python,并使用 Cryptography 包以编程方式创建证书。
这是包含代码的存储库:https://gitlab.com/0x00cl/tlscertlimits
这要简单得多,以下是我为 CA 创建私钥和证书的方法。
import datetime
from pathlib import Path
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec, rsa
from cryptography.x509.oid import NameOID
ca_key = rsa.generate_private_key(
public_exponent=65537,
key_size=16384,
)
with Path.open("certs/myCA.key", "wb") as f:
f.write(
ca_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption(),
),
)
ca_subject = ca_issuer = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, "WW"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "W" * 128),
x509.NameAttribute(NameOID.LOCALITY_NAME, "W" * 128),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "W" * 64),
x509.NameAttribute(NameOID.COMMON_NAME, "0x00.cl CA root"),
]
)
ca_cert = (
x509.CertificateBuilder()
.subject_name(ca_subject)
.issuer_name(ca_issuer)
.public_key(ca_key.public_key())
.serial_number(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0)
.not_valid_before(datetime.datetime.fromisoformat("1950-01-01 00:00:00.000+00:00"))
.not_valid_after(datetime.datetime.fromisoformat("9999-12-31 23:59:59.000+00:00"))
.add_extension(
x509.BasicConstraints(ca=True, path_length=None),
critical=True,
)
.add_extension(
x509.KeyUsage(
digital_signature=True,
content_commitment=False,
key_encipherment=False,
data_encipherment=False,
key_agreement=False,
key_cert_sign=True,
crl_sign=True,
encipher_only=False,
decipher_only=False,
),
critical=True,
)
.add_extension(
x509.SubjectKeyIdentifier.from_public_key(ca_key.public_key()),
critical=False,
)
.sign(ca_key, hashes.SHA3_512())
)
with Path.open("certs/myCA.pem", "wb") as f:
f.write(ca_cert.public_bytes(serialization.Encoding.PEM))
这要容易得多,特别是对于你可以 “乘 ”字符而不必全部写出的属性。让我们检查一下刚刚创建的证书。
$ openssl x509 -noout -text -in certs/myCA.pem
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
0f:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:f0
Signature Algorithm: RSA-SHA3-512
Issuer: C=WW, ST=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW, L=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW, O=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW, CN=0x00.cl CA root
Validity
Not Before: Jan 1 00:00:00 1950 GMT
Not After : Dec 31 23:59:59 9999 GMT
Subject: C=WW, ST=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW, L=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW, O=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW, CN=0x00.cl CA root
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (16384 bit)
Modulus:
00:ce:5e:d0:f9:e1:76:71:b2:13:20:29:2b:a6:35:
0a:39:77:56:a7:06:39:5a:82:d6:92:72:bb:9c:9a:
41:84:32:b5:31:01:67:c0:01:f6:ad:bd:d2:72:89:
# ... cut 134 lines
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints: critical
CA:TRUE
X509v3 Key Usage: critical
Digital Signature, Certificate Sign, CRL Sign
X509v3 Subject Key Identifier:
9F:CC:F1:B4:36:98:8B:8C:24:CB:0A:A2:F5:50:5C:5A:3A:AE:64:14
Signature Algorithm: RSA-SHA3-512
Signature Value:
b4:5a:57:72:68:59:85:a4:da:85:6f:f5:e8:db:44:01:a2:08:
21:de:ac:55:1b:c3:cb:51:4e:61:fd:2b:e5:93:78:45:62:b2:
af:3e:47:4b:fe:71:15:bb:37:c1:80:fb:a7:4d:49:d1:f2:85:
# ... cut 111 lines
注意:证书“模数”长 137 行,“签名值”长 114 行,它们太长了,不相关,所以我剪掉了它们。
太好了,我们有有效证书日期的最小值和最大值。
我还创建了一个中间证书,最后为一个网站创建了一个证书。因为它是以编程方式完成的,所以它就像乘以字段和列表一样简单。
web_subject = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, "WW"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "W" * 128),
x509.NameAttribute(NameOID.LOCALITY_NAME, "W" * 128),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "W" * 64),
x509.NameAttribute(NameOID.COMMON_NAME, "0x00.cl Web"),
]*4000
)
web_cert = (
x509.CertificateBuilder()
.subject_name(web_subject)
.issuer_name(inter_cert.subject)
.public_key(web_key.public_key())
.serial_number(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2)
.not_valid_before(datetime.datetime.fromisoformat("1950-01-01 00:00:00.000+00:00"))
.not_valid_after(datetime.datetime.fromisoformat("9999-12-31 23:59:59.000+00:00"))
.add_extension(
x509.SubjectAlternativeName([x509.DNSName("localhost")]),
critical=False,
)
.sign(int_key, hashes.SHA3_512())
)
生成的文件:
$ ls -l myCA*
-rw-r--r--. 1 tomas tomas 12632 Jul 03 12:00 myCAinter.key
-rw-r--r--. 1 tomas tomas 7034 Jul 03 12:00 myCAinter.pem
-rw-r--r--. 1 tomas tomas 12632 Jul 03 12:00 myCA.key
-rw-r--r--. 1 tomas tomas 6977 Jul 03 12:00 myCA.pem
-rw-r--r--. 1 tomas tomas 12632 Jul 03 12:00 myCAweb.key
-rw-r--r--. 1 tomas tomas 1067089731 Jul 03 12:00 myCAweb.pem
太好了,我们最终得到了一个 1GB 的文件。不过我必须说,在 Python 中生成具有 4000 x509 属性的列表最终确实使用了大量 RAM。我首先尝试了 40000 但崩溃了,因为它的内存用完了,而 4000 几乎无法成功,最终几乎用完了我电脑中所有的 16GB RAM。
服务证书#
现在我们已经创建了一个大证书,我们需要为它们提供服务并查看 Web 浏览器的行为方式。我决定使用 Caddy (v2.8.4) 作为 Web 服务器,只是因为我以前从未尝试过,想测试一下。这是 Caddyfile:
{
debug
http_port 8080
https_port 8443
local_certs
skip_install_trust
}
localhost {
tls ../certs/myCABundle.pem ../certs/myCAweb.key
respond "Hello, World!"
}
我下载了二进制文件,然后简单地运行它,如下所示:
$ ./caddy run -c Caddyfile
Caddy 花了大约 20 秒才开始,但它有效,没有抛出错误。
客户#
我首先尝试使用 Web 浏览器 (Firefox),但是当我尝试加载网站 (https://localhost:8443) 时,我一直收到错误。PR_END_OF_FILE_ERROR
caddy 中的日志不显示 ERROR,但它们确实显示了一个 DEBUG 日志,其中有来自 crypto/cryptobyte/builder.go 的 TLS 握手错误消息,虽然不太确定为什么会出错以及为什么它被归类为 DEBUG 而不是 ERROR,也许错误在客户端。
2024/07/03 12:00:00.000 DEBUG http.stdlib http: TLS handshake error from 127.0.0.1:42146: cryptobyte: pending child length 788004684 exceeds 3-byte length prefix
我也尝试了 curl (8.6.0),但也没有用。
$ curl -kv https://localhost:8443
* Host localhost:8443 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:8443...
* Connected to localhost (::1) port 8443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to localhost:8443
* Closing connection
curl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to localhost:8443
起初我认为可能是我没有正确创建证书,但因为我知道我正在尝试加载一个 1GB 的证书(充其量只有几 kB),所以我尝试使用较小的证书,这确实有效。我再次尝试加载大证书,但再次出现同样的错误。也许证书的大小是有限制的。我尝试减少字段数量以将证书的大小减小到几 MB,但这也没有用,所以我慢慢减小了证书的大小,直到收到新的错误。
新证书的大小为 1.1MB。使用 firefox 时,我收到错误 。SSL_ERROR_RX_MALFORMED_HANDSHAKE
球童的“错误”也不同,卷曲也不同。
2024/07/11 15:50:29.281 DEBUG http.stdlib http: TLS handshake error from 127.0.0.1:47612: remote error: tls: error decoding message
$ curl -vk https://localhost:8443
* Host localhost:8443 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:8443...
* Connected to localhost (::1) port 8443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (OUT), TLS alert, illegal parameter (559):
* OpenSSL/3.2.1: error:0A000098:SSL routines::excessive message size
* Closing connection
curl: (35) OpenSSL/3.2.1: error:0A000098:SSL routines::excessive message size
至少 curl 现在显示错误的实际消息,即 “over message size”。看起来 curl 不喜欢 “大” 证书,并且它有限制。由于 curl 知道证书太大,那么必须编码或配置限制,所以我开始查看 git 中的源代码,并在手册页中发现最大证书链大小为 100kB。从源代码中我能理解的内容来看,这是在 ssl/statem/statem_local.h 中定义的(102400 字节 = 100kB)。
因此,我尝试通过将重复字段的数量减少到 221 个,使其在 openssl 允许的范围内尽可能大 (100kB),并且它奏效了:
$ curl -vk https://localhost:8443
* Host localhost:8443 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:8443...
* Connected to localhost (::1) port 8443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / x25519 / RSASSA-PSS
* ALPN: server accepted h2
* Server certificate:
* subject: C=WW; ST=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; L=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; O=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; CN=0x00.cl Web; C=WW; ST=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; L=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; O=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; CN=0x00.cl Web; C=WW; ST=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; L=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; O=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; CN=0x00.cl Web; C=WW; ST=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; L=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; O=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; CN=0x00.cl Web; C=WW; ST=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; L=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; O=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; CN=0x00.cl Web; C=WW; ST=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; L=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW
* start date: Jan 1 00:00:00 1950 GMT
* expire date: Dec 31 23:59:59 9999 GMT
* issuer: C=WW; ST=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; L=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; O=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; CN=0x00.cl CA Intermediate
* SSL certificate verify result: self-signed certificate in certificate chain (19), continuing anyway.
* Certificate level 0: Public key type RSA (16384/272 Bits/secBits), signed using RSA-SHA3-512
* Certificate level 1: Public key type RSA (16384/272 Bits/secBits), signed using RSA-SHA3-512
* Certificate level 2: Public key type RSA (16384/272 Bits/secBits), signed using RSA-SHA3-512
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://localhost:8443/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: localhost:8443]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.6.0]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: localhost:8443
> User-Agent: curl/8.6.0
> Accept: */*
>
< HTTP/2 200
< alt-svc: h3=":8443"; ma=2592000
< content-type: text/plain; charset=utf-8
< server: Caddy
< content-length: 13
< date: Fri, 03 Jul 2024 12:00:00 GMT
<
* Connection #0 to host localhost left intact
Hello, World!
通过在 curl 中使用 flag ,我们可以获得有关数据交换的更多信息。--trace-ascii -
$ curl -k --trace-ascii - https://localhost:8443
...
== Info: TLSv1.3 (IN), TLS handshake, Certificate (11):
<= Recv SSL data, 102404 bytes (0x19004)
...
它收到了 102404 个字节的证书,这比 openssl 源代码中的限制仅差 4 个字节,不确定 curl 或 openssl 是如何计算这些字节的,但即使只添加 1 个字符(应该是 1 个字节)也会使 curl (openssl) 抛出错误。
我发现有趣的是,我尝试使用 Firefox 发出请求,但它抛出了一条错误消息,与以前的不同,尽管它与 curl 一起工作。那时我开始阅读更多内容,发现 Firefox 使用 nss,虽然我找不到他们在哪里设置证书大小限制,但它似乎将大小限制为证书的扩展(部分)。例如,证书的主题我可以在开始引发错误之前将 curl 的值乘以 221 次,而 Firefox 只允许 111 次,但字段“SubjectAlternativeName”我可以“扩展”它至少 1000 次,Firefox 仍然可以工作,即使它已经达到主题字段的大小限制。SEC_ERROR_BAD_DER
最后#
探索 TLS 证书当然很有趣,我确实学到了很多关于它们的知识,特别是因为我不得不多次阅读 RFC 5280,但也有点遗憾的是,虽然 1GB 证书在技术上可以工作,但最终它会受到客户端的限制,例如您的 Web 浏览器或 CLI 工具,如 curl。我确实希望能够做一些“更疯狂”的事情,例如能够使用负日期或超过 9999 年的日期,例如,openssl 确实允许设置低于 1950 年的日期,例如 0000,但这不符合 RFC 5280 标准,并且 python 包加密确实将限制设置为 1950。
在这篇博客的介绍中,我想知道:
- 证书可以有多大?curl 工作需要 100kB,而像 Firefox 这样的 Web 浏览器需要 ~60kB,尽管 Firefox TLS 库的工作方式不同,因此结果可能会有所不同。
- 它能持续多久?从 1950 年 1 月 1 日到 9999 年 12 月 31 日。大约 8050 年。
希望您也很容易理解,并且也从此博客中学到了一些关于 TLS 证书的知识。:)
本作品由 Tomás Gutiérrez L. 创作,采用 Creative Commons Attribution-ShareAlike 4.0 International License 进行许可。
详细内容见:
探索 TLS 证书及其限制 - 0x00https://0x00.cl/blog/2024/exploring-tls-certs/