由中间CA(链)签名的NGinx SSL证书认证

Posted

技术标签:

【中文标题】由中间CA(链)签名的NGinx SSL证书认证【英文标题】:NGinx SSL certificate authentication signed by intermediate CA (chain) 【发布时间】:2012-01-15 22:06:41 【问题描述】:

我正在尝试在证书已由中间 CA 签名的 nginx 中启用客户端证书身份验证。使用由自签名根 CA 签名的证书时,我能够正常工作;但是,当签名 CA 是中间 CA 时,这不起作用。

我的简单服务器部分如下所示:

server 
    listen       443;
    server_name  _;

    ssl                  on;
    ssl_certificate      cert.pem;
    ssl_certificate_key  cert.key;

    ssl_session_timeout  5m;

    ssl_protocols  SSLv2 SSLv3 TLSv1;
    ssl_ciphers  ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
    ssl_prefer_server_ciphers   on;

    ssl_client_certificate ca.pem;
    ssl_verify_client on;
    ssl_verify_depth 1;

    location / 
        root   html;
        index  index.html index.htm;
    

对于 ca.pem 的内容,我尝试使用 中间 CA 并将中间 CA 证书和根 CA 证书连接起来,例如:

cp intermediate.crt ca.pem
cat root.crt >> ca.pem

当使用相同的 CA 链时,我还验证了从 openssl 的角度来看证书是有效的:

openssl verify -CAfile /etc/nginx/ca.pem certs/client.crt 
certs/client.crt: OK

我已经尝试将 ssl_verify_depth 显式设置为 1(如上),然后设置为 0(不确定该数字的确切含义),但仍然出现相同的错误。

我在中间 CA 的所有变体中遇到的错误是“400 Bad Request”,更具体地说是“SSL 证书错误”(不确定这到底是什么意思)。

也许 nginx 只是不支持中间证书的证书链?非常感谢任何帮助!

【问题讨论】:

【参考方案1】:

更新: 我相信您想在服务器端启用客户端验证。如果是这样,请正确创建证书链。以完全相同的顺序尝试以下操作。使用 certchain.pem。

  cat intermediate.crt >> certchain.pem
  cat root.crt >> certchain.pem

【讨论】:

这对我来说没有意义。我没有一个客户证书(必须);每个连接的客户端都有自己的证书,由同一个中间 CA 签名。 在启用客户端身份验证的情况下,应该能够访问服务器的每个客户端都应该受到服务器的信任。这意味着您需要事先将每个客户端证书导入服务器信任库。信任链向前传播,因此您需要先导入客户端证书,然后是中介,然后是根。 这没有任何意义,并且完全违背了拥有一个共同的证书颁发机构来签署这些证书的意义。另外,我应该指出,在使用根 CA 时我不需要附加客户端证书;效果很好。只有在使用中间 CA 时才会出现问题。 哦,是的,你是对的!不需要导入客户端证书。只需要中间人和根 CA 证书。很抱歉造成混乱。 您可以省略 ssl_client_certificate 并将 ssl_certificate 中的所有内容连接起来,正如 Vikas 指出的那样。【参考方案2】:

@Jack 和@HansL,一种只允许来自一个 IntermediateCA1 的客户端的解决方案是使用 nginx config ssl_trusted_certificate。来自nginx documentation:

指定具有 PE​​M 格式的受信任 CA 证书的文件,用于 如果启用了 ssl 装订,请验证客户端证书和 OCSP 响应。 与 ssl 客户端证书设置的证书相比,这些列表 证书不会发送给客户

这些设置对我有用(当然也适用于其他 PKI)。在第一个文件中只有IntermediateCA1 存在。在后者只有RootCA

ssl_client_certificate     /etc/nginx/ssl/ca-bundle-for-client-selection-filtering.crt;
ssl_trusted_certificate    /etc/nginx/ssl/ca-bundle-for-client-cert-valdiation.crt;  

【讨论】:

这不是一个安全的解决方案,它所做的只是防止在您的浏览器中使用漂亮的 UI 提示选择证书,而真正的攻击者可能只是强行发送最终由任何一方签名的证书根或中间。 是的,您需要通过其他方式验证提供的客户端证书是由 IntermediateCA 而不是 RootCA 颁发的。这一切都取决于您拥有的 PKI 和信任的生态系统。 但这是过滤用户选择对话框的唯一方法,以便可以选择由 IntermediateCA1 而不是 IntermediateCA2 颁发的证书,但仍然有 nginx 验证根。如果将 rootCA 放在 ssl_client_certificate 中,用户无法提供中间证书,但来自两个 CA 的证书都可以选择并被 nginx 接受,在这个特定用例中我们不希望这样做。对吗? 是的,对于 UX 来说,简化他们对证书的选择非常好,我只需要发表评论来尝试帮助人们意识到,仅仅因为它减少了你在构建它时可以选择的客户端,它不是t 实际上禁止这些证书起作用,人们可能认为这意味着,特别是因为这是 OP 想要实现的目标 感谢@Magnum,此答案与已接受答案中的颁发者检查相结合,是最完整的解决方案。【参考方案3】:

ssl_verify_depth -> sets a verification depth in the client certificates chain

Authority 提供了一系列链式证书,这些证书应连接到签名的服务器证书。服务器证书必须出现在组合文件中的链式证书之前

'$ openssl s_client -connect www.godaddy.com:443

...

Certificate chain
 0 s:/C=US/ST=Arizona/L=Scottsdale/1.3.6.1.4.1.311.60.2.1.3=US
     /1.3.6.1.4.1.311.60.2.1.2=AZ/O=GoDaddy.com, Inc
     /OU=MIS Department/CN=www.GoDaddy.com
     /serialNumber=0796928-7/2.5.4.15=V1.0, Clause 5.(b)
   i:/C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc.
     /OU=http://certificates.godaddy.com/repository
     /CN=Go Daddy Secure Certification Authority
     /serialNumber=07969287
 1 s:/C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc.
     /OU=http://certificates.godaddy.com/repository
     /CN=Go Daddy Secure Certification Authority
     /serialNumber=07969287
   i:/C=US/O=The Go Daddy Group, Inc.
     /OU=Go Daddy Class 2 Certification Authority
 2 s:/C=US/O=The Go Daddy Group, Inc.
     /OU=Go Daddy Class 2 Certification Authority
   i:/L=ValiCert Validation Network/O=ValiCert, Inc.
     /OU=ValiCert Class 2 Policy Validation Authority
     /CN=http://www.valicert.com//emailAddress=info@valicert.com'

在此示例中,www.GoDaddy.com 服务器证书 #0 的主题 (“s”) 由发行者 (“i”) 签名,该发行者 (“i”) 本身就是证书 #1 的主题,该证书由发行者本身是证书#2 的主体,该证书由知名发行者 ValiCert, Inc. 签名,其证书存储在浏览器的内置证书库中

Nginx 使用ssl_verify_depth 指令挖掘证书包,以验证其受信任存储中的颁发者,该存储在proxy_ssl_trusted_certificate

【讨论】:

【参考方案4】:

我不得不说 nginx/1.13.2 对我来说工作正常,即

我有一个根 CA 签署了两个中间 CA 两个中间人都签了一个客户

我将证书连接起来 cat client-intermediate1.crt ca-client.crt > ca.chained1.crtcat client-intermediate2.crt ca-client.crt > ca.chained2.crtcat ca.chained1.crt ca.chained2.crt > ca.multiple.intermediate.crt

如果我只将 ca.chained1.crt 设为 ssl_client_certificate,那么只有 client1.crt 可以连接,对于 ca.chained2.crt/client2.crt 也是如此

当我使用ca.multiple.intermediate.crt 时,两个客户端都可以连接

要撤销中间件,只需从 ca.multiple.intermediate.crt 中删除证书链

这里是相关配置。它还具有高安全性设置

# minimum settings for ssl client auth 
ssl_client_certificate /etc/ssl/ca.multiple.intermediate.crt;
ssl_verify_client on;
ssl_verify_depth 2;

# ssl high security settings (as of writing this post)
ssl_protocols TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_ecdh_curve secp384r1;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;

如果您想解析出证书 CN 并将其传递给后端,则将其添加到 server .. 块之外

# parse out CN
map $ssl_client_s_dn $ssl_client_s_dn_cn 
    default "should_not_happen";
    ~CN=(?<CN>[^,]+) $CN;

然后在块内你可以使用它

# add headers for backend containing SSL DN/CN
add_header X-SSL-client-s-dn $ssl_client_s_dn;
add_header X-SSL-client-s-dn_cn $ssl_client_s_dn_cn;

【讨论】:

@HansL ye 我认为 nginx 在这方面得到了解决。我没有注意到 Jacks 接受的答案的关键区别可以解释它 感谢您展示这一点,但您可能想知道,简单地从您的 ca.multiple.intermediate.crt 中删除证书链并不会撤销您的中间件。如果客户端可以提供与客户端证书捆绑在一起的中间 ca,客户端仍然能够进行身份验证,例如: curl --cert 任何 可链接的根,它将进行身份验证。 不!我没有看到这种行为(Nginx 1.14.2-2)。将证书链放入ssl_client_certificate 的文件中允许任何从根 CA 派生(直接或非直接)的客户端。这与 doc 表示文件是授权 CA 的列表是一致的。 @mimo 我也在观察这种行为,所以我认为这个答案不准确【参考方案5】:

当我在 nginx 和 cloudflare 苦苦挣扎时, 这些行对我有用:

ssl_client_certificate    /etc/nginx/ssl/ca-bundle-client.crt;  
ssl_verify_client optional_no_ca;  
ssl_verify_depth 2;

带有 optional_no_ca 的第二行是重要部分

【讨论】:

这只是完全禁用 CA 验证!如果您使用客户端验证来确保只有合适的人可以使用该服务,那将是一件非常糟糕的事情。【参考方案6】:

另一种简单的方法是将证书(包括域证书)连接到一个文件中,并在您的服务器和 nginx conf 文件中使用它

cat www.example.com.crt bundle.crt > www.example.com.chained.crt

始终记得先使用服务器证书,然后再使用 CA 服务器证书

您可以在http://nginx.org/en/docs/http/configuring_https_servers.html#chains了解更多信息

【讨论】:

你读过这个问题吗?当然,我尝试连接证书;这是非常标准的 SSL 做法......我在您提供的链接中没有看到任何关于客户端证书身份验证的信息。 @HansL - 我理解的是,您在使用中间 CA 颁发的证书时遇到了问题。这仅仅意味着您的证书链缺少链接到根 CA 的权限。如果您在单个文件中连接证书(包括链到根 CA),Nginx 可以提供帮助。我希望您关注的页面部分是nginx.org/en/docs/http/configuring_https_servers.html#chains。 是的,但在我的问题中,我明确表示我尝试将它们连接在一起。是的,这适用于服务器证书,但不适用于客户端身份验证(至少在撰写本文时),这就是为什么我的问题专门针对客户端身份验证的 SSL 证书。【参考方案7】:

编辑:我也有这个“问题”,解决方案和解释在文本的底部

好像 nginx 不支持中间证书。我自己创建的证书:(RootCA 是自签名的,IntremediateCA1 是由 RootCA 签名的,等等)

RootCA -> IntermediateCA1 -> Client1 
RootCA -> IntermediateCA2 -> Client2

我想在 nginx“IntermediateCA1”中使用,只允许“Client1”证书的所有者访问站点。

当我将 IntermediateCA1 和 RootCA 放入“ssl_client_certificate”文件并设置“ssl_verify_depth 2”(或更多)时,客户端可以同时使用证书 Client1 和 Client2 登录网站strong>(应该只有 Client1)。 同样的结果是当我使用仅 RootCA 放入“ssl_client_certificate”文件时 - 两个客户端都可以登录。

当我将 IntermediateCA1 放入“ssl_client_certificate”文件并设置“ssl_verify_depth 1”(或“2”或更多 - 无论如何)时,无法登录,我收到错误400. 在调试模式下,我看到了日志:

verify:0, error:20, depth:1, subject:"/C=PL/CN=IntermediateCA1/emailAddress=cert@asdf.com",issuer: "/C=PL/CN=RootCA/emailAddress=cert@asdf.com"
verify:0, error:27, depth:1, subject:"/C=PL/CN=IntermediateCA1/emailAddress=cert@asdf.com",issuer: "/C=PL/CN=RootCA/emailAddress=cert@asdf.com"
verify:1, error:27, depth:0, subject:"/C=PL/CN=Client1/emailAddress=cert@asdf.com",issuer: "/C=PL/CN=IntermediateCA1/emailAddress=cert@asdf.com"
(..)
client SSL certificate verify error: (27:certificate not trusted) while reading client request headers, (..)

我认为这是一个错误。在 Ubuntu、nginx 1.1.19 和 1.2.7-1~dotdeb.1、openssl 1.0.1 上测试。 我看到 nginx 1.3 关于使用客户端证书的选项很少,但我没有看到解决此问题的方法。

目前,分离客户端 1 和 2 的唯一方法是创建两个自签名的 RootCA,但这只是解决方法..

编辑 1: 我在这里报告了这个问题:http://trac.nginx.org/nginx/ticket/301

编辑 2" *好吧,这不是错误,这是功能;)*

我在这里得到回复:http://trac.nginx.org/nginx/ticket/301 它正在工作,您必须只检查您的 ssl_client_i_dn 是什么(除了颁发者之外,您还可以使用证书的主题,或者您想要来自 http://wiki.nginx.org/HttpSslModule#Built-in_variables 的内容

这是证书验证的工作原理:证书必须是 验证到受信任的根。如果无法将链构建到受信任的 根(非中间) - 验证失败。如果您信任 root - all 由它直接或间接签署的证书将被 成功验证。

可以使用限制验证深度,如果您 想要将客户端证书限制为直接颁发的证书 只是,但更多的是关于 DoS 预防,显然不可能 用于将验证限制为仅中间 1(但不是 中级2)。

你想要的是一些基于授权层的 关于验证结果 - 即您可能想要检查该客户的 证书颁发者是中间人1。最简单的解决方案是 如果颁发者的 DN 与允许的 DN 不匹配,则拒绝请求,例如 像这样的东西(完全未经测试):

[由我编辑,它在我的配置中正常工作]

server 
    listen 443 ssl;

    ssl_certificate ...
    ssl_certificate_key ...

    ssl_client_certificate /path/to/ca.crt;
    ssl_verify_client on;
    ssl_verify_depth 2;

    if ($ssl_client_i_dn != "/C=PL/CN=IntermediateCA1/emailAddress=cert@asdf.com") 
        return 403;
    

【讨论】:

要确定客户端证书的颁发者 DN,请使用以下命令:openssl x509 -noout -in &lt;/path/to/client/certificate.crt&gt; -issuer 我认为中间证书存在的理由是它们可以被撤销(而根,仅用于签署中间证书,可以得到更好的保护,并且只偶尔在空中使用-间隙系统或通过硬件安全模块)。那么如何使用这个 Nginx 设置进行中间证书吊销呢? ssl_crl 参数仅用于客户端证书本身。 我有同样的问题,没有额外的身份验证就解决了。我认为你不需要自己检查 $ssl_client_i_dn 即使你不信任中间,应该使用 if ($ssl_client_verify != SUCCESS) 代替。检查证书是否使用名为“clientAuth”的属性签名:openssl x509 -in /path/to/client/cert -noout -purpose | grep 'SSL client :' 查看此链接:serverfault.com/questions/875229/… 它对我有用! @DisplayName 如果它的颁发者被吊销,您的服务器不会验证客户端证书失败吗? @AmrAshraf 我认为您在这里指的是完全不同的事情【参考方案8】:

您是否尝试过增加ssl_verify_depth 指令? Docs说:

(it) sets a verification depth in the client certificates chain.

但你的验证深度是 1。你说:

我已经尝试将 ssl_verify_depth 显式设置为 1(如上),然后设置为 0(不确定该数字的确切含义),但仍然出现相同的错误。

所以,试试 2 或 3..

PS: 在我发现提到这个问题的任何地方,它都被告知将中间 CA 证书与您的服务器证书结合起来。按验证顺序放入一个文件(如@vikas-nalwar suggested 和您所做的那样)(但我不确定顺序是否重要),大致将ssl_verify_depth 设置为捆绑包中的证书数量。

【讨论】:

这个建议很有道理;我似乎记得也尝试过更大的数字,但我可能错了。文档确实建议这是我想转动的旋钮,你是对的。我会尝试找一些时间来验证这一点(由于其他领域的问题,我们放弃了 nginx,所以我没有现成的设置来测试这个理论,但应该不会花很长时间重新创建)。 订单很重要;服务器证书需要位于组合/链接文件中的第一个 根据我的测试,如果链长 3 个证书,将 ssl_verify_depth 设置为 3 不会强制长度为 3,而是验证将在 3 个证书后停止,并成功,或者如果您发送由 Root 直接签名的证书(长度为 2 个证书)它也成功,因为它到达了 Root。

以上是关于由中间CA(链)签名的NGinx SSL证书认证的主要内容,如果未能解决你的问题,请参考以下文章

详解SSL证书之证书链

NGINX 配置 SSL 双向认证

自签名SSL证书是啥?有用吗

自签名SSL证书是啥?有用吗?

如何创建ssl证书 nginx

如何生成自签名双向认证证书 ssl