openssl 会拒绝没有基本约束的自签名证书吗?

信息安全 tls openssl x.509
2021-09-05 22:03:55

我有两个极其相似的自签名证书,是通过两种不同的方法生成的。

为了测试它们,我有:

  1. 在我的主机文件中为 local.mydomain.com 添加了一个条目
  2. 设置一个 nginx 服务器以在端口 443 上使用正在测试的证书和关联的私钥监听该域(然后我切换证书并重新启动 nginx 进行比较)
  3. 连接到nginx openssl s_client -connect local.mydomain.com -CAfile /path/to/the/ca/cert.pem

一个证书失败:

CONNECTED(00000003)
depth=0 CN = local.mydomain.com
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN = local.mydomain.com
verify error:num=21:unable to verify the first certificate
verify return:1
---
Certificate chain
 0 s:/CN=local.mydomain.com
   i:/CN=local.mydomain.com
---

一个证书成功:

CONNECTED(00000003)
depth=0 CN = local.mydomain.com
verify return:1
---
Certificate chain
 0 s:/CN = local.mydomain.com
   i:/CN = local.mydomain.com
---

我将证书的详细信息与 openssl x509 -in /path/to/the/ca/cert.pem -text -noout

失败的证书:

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            47:dc:02:c7:11:fc:8e:96:45:22:aa:6b:23:79:32:ca
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=local.mydomain.com
        Validity
            Not Before: Nov 18 11:55:31 2016 GMT
            Not After : Nov 18 12:15:31 2017 GMT
        Subject: CN=local.mydomain.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    <stuff>
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage:
                TLS Web Client Authentication, TLS Web Server Authentication
            X509v3 Subject Alternative Name:
                DNS:local.mydomain.com
            X509v3 Subject Key Identifier:
                6D:4F:AF:E4:60:23:72:E5:83:27:91:7D:1D:5F:E9:7C:D9:B6:00:2A
    Signature Algorithm: sha256WithRSAEncryption
         <stuff>

工作证书:

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            9b:6b:3d:a3:b9:a3:a4:b4
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=local.mydomain.com
        Validity
            Not Before: Nov 19 13:27:30 2016 GMT
            Not After : Nov 19 13:27:30 2017 GMT
        Subject: CN=local.mydomain.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    <stuff>
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                03:E7:DA:AA:2E:CC:23:ED:C5:07:3D:E1:33:86:F5:22:D4:76:EB:CB
            X509v3 Authority Key Identifier:
                keyid:03:E7:DA:AA:2E:CC:23:ED:C5:07:3D:E1:33:86:F5:22:D4:76:EB:CB

            X509v3 Basic Constraints:
                CA:TRUE
    Signature Algorithm: sha256WithRSAEncryption
         57<stuff>

看这个最明显的区别是工作证书CA:TRUE低于X509v3 Basic Constraints. 但是,从网上阅读我的印象是,自签名证书并不意味着是 CA,特别是这表明它们通常不会是:

基本的自签名证书问题

那里的答案说自签名不涉及CA。但是也许openssl需要自签名证书才能设置?或者证书可能以其他相关方式有所不同?

更新:

我花了一些时间尝试 printf 调试 openssl 的内部结构,但我并不了解它。在文件 v3_purp.c 中有以下宏:

#define ku_reject(x, usage) \
        (((x)->ex_flags & EXFLAG_KUSAGE) && !((x)->ex_kusage & (usage)))

它用于检查自签名证书的这段代码中:

/* Does subject name match issuer ? */

if (X509_check_akid(x, x->akid) == X509_V_OK &&
            !ku_reject(x, KU_KEY_CERT_SIGN))
            x->ex_flags |= EXFLAG_SS;

在失败证书的情况下(x)->ex_flags & EXFLAG_KUSAGE等于 2

EXFLAG_KUSAGE 在这里为失败的证书设置,早先在同一个文件中:

static void x509v3_cache_extensions(X509 *x)
{
    ......
    if ((usage = X509_get_ext_d2i(x, NID_key_usage, NULL, NULL))) {
        if (usage->length > 0) {
            x->ex_kusage = usage->data[0];
            if (usage->length > 1)
                x->ex_kusage |= usage->data[1] << 8;
        } else
            x->ex_kusage = 0;
        x->ex_flags |= EXFLAG_KUSAGE;
        ASN1_BIT_STRING_free(usage);
    }
    ....

所以看起来问题是失败的证书具有X509v3 Key Usage扩展名,但没有在该扩展名中指定 KU_KEY_CERT_SIGN?

更新 2:

根据https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.3

“当主题公钥用于验证公钥证书上的签名时,keyCertSign 位被断言。如果 keyCertSign 位被断言,那么基本约束扩展(第 4.2.1.9 节)中的 cA 位也必须被断言。”

因此,基本约束中的 CA 位不必存在,但如果您在证书中包含 X509v3 密钥使用部分,则根据 openssl 代码库,您必须指定 keyCertSign,并且根据 RFC,如果您确实指定了 keyCertSign,那么您还必须包括CA位的基本约束?

2个回答

问题中描述的问题可以通过更简单的设置来重现。我创建了两个用于测试的自签名证书,它们的区别仅在于一个具有 CA 标志 ( ss-ca.pem) 而另一个没有 ( ss-noca.pem)。有了openssl verify一个可以检查证书可以针对特定的CA路径进行验证。

带有 CA:true 的自签名证书已成功验证自身(“OK”),尽管它在验证链时偶然发现 X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT(错误 18):

$ openssl verify -CAfile ss-ca.pem ss-ca.pem 
ss-ca.pem: CN = test CA
error 18 at 0 depth lookup:self signed certificate
OK

使用带有 CA:false 的自签名证书,验证不成功(没有“OK”),并显示错误 X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY(错误 20):

$ openssl verify -CAfile ss-noca.pem ss-noca.pem 
ss-noca.pem: CN = test no CA
error 20 at 0 depth lookup:unable to get local issuer certificate

我的理论是 OpenSSL 尝试将信任链构建到使用-CAfile. 要建立信任链,颁发者证书主体必须与证书颁发者匹配,签名必须有效(即使用颁发者公钥进行验证)并且必须允许颁发者证书签署证书,即 CA:true。虽然前两项检查对两个证书都没有问题,但 CA:true 的检查仅对ss-ca.pem.

-CAfile我认为 OpenSSL与信任库的其他概念的主要区别在于,-CAfile它只顾名思义:它包含一个可信 CA 列表,用于验证信任链。与此相反,其他信任存储实现还可能包含任何类型的证书,其中验证例程仅检查服务器发送的证书是否直接受信任,因为它包含在信任存储中,无论证书具有何种标志,甚至如果过期了。

-CAfile将非 CA 最终实体证书作为受信任放入存储时,也可以看到通用信任存储和 OpenSSL 之间的这种差异。在示例中,我使用了一个 CA 证书,该证书颁发了一个当然没有 CA:trueca.pem的最终实体证书。server.pem

根据 CA 证书验证最终实体证书按预期工作:

$ openssl verify -CAfile ca.pem server.pem
server.pem: OK

但是尝试通过将最终实体证书放入 CA 存储来直接信任它是行不通的,因为 CA 存储不是通用信任存储,但仅限于 CA 证书:

$ openssl verify -CAfile server.pem server.pem
error 20 at 0 depth lookup:unable to get local issuer certificate

对于通用信任存储,最后一次验证也应该成功,因为最终实体证书被明确声明为受信任。

这个问题今天已在 OpenSSL 中得到修复(参见https://github.com/openssl/openssl/issues/1418);应该很快就可以使用了。