8

我想确保客户端库(目前在 Python、Ruby、PHP、Java 和 .NET 中)配置正确,并且在 SSL 证书无效时适当地失败。Shmatikov 的论文《世界​​上最危险的代码:在非浏览器软件中验证 SSL 证书》揭示了 SSL 验证是多么令人困惑,因此我想彻底测试可能的故障。

根据研究,如果出现以下情况,则证书无效:

  • 它在其激活日期之前使用
  • 它在到期日之后使用
  • 已被撤销
  • 证书主机名与站点主机名不匹配
  • 证书链不包含受信任的证书颁发机构

理想情况下,我认为每个无效案例都有一个测试案例。为此,我目前正在测试一个HTTP访问过的站点HTTPS,这会导致失败,我可以在这样的测试中进行验证:

self.assertRaises(SSLHandshakeError, lambda: api.call_to_unmatched_hostname())

这是不完整的(仅涵盖一种情况)并且可能是错误的,所以......

如何测试非浏览器软件正确验证 SSL 证书?

4

2 回答 2

6

首先,您需要一组 SSL 证书,其中每个证书都有一个问题。您可以使用 openssl 命令行工具生成这些。当然,您不能使用受信任的根 CA 对它们进行签名。您将需要使用自己的 CA。要正确验证,您需要在客户端库中安装 CA 证书。您可以在 Java 中执行此操作,例如,使用控制面板。

获得证书后,您可以使用“openssl s_server”工具为每个证书提供 SSL 套接字。我建议您在每个端口上放置一个证书。

您现在必须使用客户端库连接到端口,并验证您是否收到正确的错误消息。

我知道 Python 默认不进行证书验证(查看 httplib.HTTPSConnection 的手册)。但是,m2crypto 确实进行了验证。默认情况下,Java 会进行验证。我不知道其他语言。

您可以测试的其他一些情况:1)通配符主机名。2) 证书链接。我知道旧浏览器中存在一个错误,如果您有一个由根签名的证书 A,那么 A 可以签名 B,并且 B 看起来是有效的。SSL 应该通过在证书上有标志来阻止这种情况,而 A 不会有“可以签名”标志。但是,这在一些旧浏览器中没有得到验证。

祝你好运!我很想听听你过得怎么样。

保罗

于 2013-06-24T17:27:34.363 回答
5
  • 证书主机名与站点主机名不匹配

    这可能是最容易检查的,而失败(失败)肯定有很好的迹象表明出了点问题。大多数知名服务的证书仅使用主机名作为其身份,而不是 IP 地址。如果,而不是要求https://www.google.com/,您要求https://173.194.67.99/(例如)并且它有效,那么就有问题了。

对于其他的,您可能想要生成自己的测试 CA。

  • 证书链不包含受信任的证书颁发机构

    您可以使用您的测试 CA(或自签名证书)生成测试证书,但让默认系统 CA 列表用于验证。您的测试客户端应该无法验证该证书。

  • 在激活日期之前使用,在到期日期之后使用

    您可以使用您的测试 CA 生成测试证书,notBefore/notAfter 日期使当前日期无效。然后,使用您的测试 CA 作为受信任的 CA 进行验证:您的测试客户端应该因为日期而无法验证证书。

  • 已被撤销

    这可能是最难设置的,具体取决于撤销的发布方式。同样,使用您自己的测试 CA 生成一些您已立即撤销的测试证书。一些工具期望在一组受信任的 CA 旁边配置一组 CRL 文件。这需要对测试本身进行一些设置,但很少进行在线设置:这可能是最简单的。您还可以设置本地在线撤销存储库,例如使用 CRL 分发点或 OCSP。

PKI 测试可能比一般情况下更复杂。完整的测试套件需要对规范有相当好的理解(RFC 5280)。实际上,您可能需要检查所有中间证书的日期,以及链中每个证书的各种属性(例如密钥使用、基本约束……)。

通常,客户端库将验证过程分为两个操作:验证证书是否可信(PKI 部分)和验证它是否已颁发给您要连接的实体(主机名验证部分)。这当然是因为这些是在不同的文档中指定的(分别为 RFC 3280/5280 和 RFC 2818/6125)。

从实际的角度来看,使用 SSL 库时要检查的前两点是:

  • 当您连接到已知主机但使用证书无效的不同标识符(例如其 IP 地址而不是主机)时会发生什么?
  • 当您连接到您知道无法通过任何默认的受信任锚点(例如,自签名证书或来自您自己的 CA)验证的证书时会发生什么。

在这两种情况下都应该发生连接/验证失败。如果一切正常,没有实现完整的 PKI 测试套件(需要一定的专业知识),通常情况下您需要检查该 SSL 库的文档以了解如何打开这些验证。

除了错误之外,本文中提到的相当多的问题是由于一些库实现假设由用户知道他们在做什么,而他们的大多数用户似乎已经做出了假设该库默认情况下正在做正确的事情。(事实上​​,即使库默认做正确的事情,当然也不缺少只想摆脱错误消息的程序员,即使这会使他们的应用程序不安全。)我可以公平地说在大多数情况下,确保启用验证功能就足够了。

至于一些现有实现的状态:

  • Python:Python 2.x 和 Python 3.x 之间发生了变化。Python 3.2的ssl模块有一个match_hostnamePython 2.7 没有的方法。urllib.request.urlopen在 Python 3.2 中也有一个配置 CA 文件的选项,它的 Python 2.7 等效项没有。(话虽这么说,如果没有设置,验证将不会发生。我不确定主机名验证。)

  • Java:默认情况下,对 PKI 和主机名的验证都是打开的HttpsUrlConnection,但在直接使用时不会对主机名进行验证SSLSocket,除非您使用的是 Java 7 并且您已经配置了它的SSLParameters使用setEndpointIdentificationAlgorithm("HTTPS")(例如)。

  • PHP:据我所知,fopen("https://.../")根本不会执行任何验证。

于 2013-06-24T18:22:28.063 回答