【问题标题】:Python Urllib2 SSL errorPython Urllib2 SSL 错误
【发布时间】:2015-03-04 11:27:09
【问题描述】:

Python 2.7.9 现在对 SSL 证书验证更加严格。太棒了!

对于以前运行的程序现在出现 CERTIFICATE_VERIFY_FAILED 错误,我并不感到惊讶。但我似乎无法让它们工作(不完全禁用证书验证)。

一个程序使用 urllib2 通过 https 连接到 Amazon S3。

我将根 CA 证书下载到名为“verisign.pem”的文件中并尝试以下操作:

import urllib2, ssl
context = ssl.create_default_context()
context.load_verify_locations(cafile = "./verisign.pem")
print context.get_ca_certs()
urllib2.urlopen("https://bucket.s3.amazonaws.com/", context=context)

我仍然收到 CERTIFICATE_VERIFY_FAILED 错误,即使根 CA 在第 4 行中正确打印出来。

openssl 可以正常连接到这个服务器。其实这里是我用来获取CA证书的命令:

openssl s_client -showcerts -connect bucket.s3.amazonaws.com:443 < /dev/null

我将链中的最后一个证书放入一个 PEM 文件中,openssl 可以正常读取。这是带有以下内容的 Verisign 证书:

Serial number: 35:97:31:87:f3:87:3a:07:32:7e:ce:58:0c:9b:7e:da
Subject key identifier: 7F:D3:65:A7:C2:DD:EC:BB:F0:30:09:F3:43:39:FA:02:AF:33:31:33
SHA1 fingerprint: F4:A8:0A:0C:D1:E6:CF:19:0B:8C:BC:6F:BC:99:17:11:D4:82:C9:D0

任何想法如何在启用验证的情况下使其工作?

【问题讨论】:

  • verisign.pem 中有哪个证书?确保它是 SHA1 指纹为 A1:DB:63:93:91:6F:17:E4:18:55:09:40:04:15:C7:02:40:B0:AE:6B(3 类公共主要证书颁发机构)的证书,而不是带有 4E:B6:D5:78:49:9B:1C:CF:5F:58:1E:AD:56:BE:3D:9B:67:44:A5:E5(威瑞信 3 类公共主要证书颁发机构 - G5)的证书
  • 我终于找到了,它可以工作,谢谢!如何判断正在使用哪个根证书?我正在使用 openssl x509 命令转储链中的每个证书。他们中的大多数人说他们是用哪个证书签名的,但我看不到最后一个说它是用哪个根证书签名的。

标签: python ssl urllib2


【解决方案1】:

对问题原因进行总结,并更详细地解释真正的问题:

如果您检查 OpenSSL 客户端的信任链,您会得到以下信息:

 [0] 54:7D:B3:AC:BF:... /CN=*.s3.amazonaws.com 
 [1] 5D:EB:8F:33:9E:... /CN=VeriSign Class 3 Secure Server CA - G3
 [2] F4:A8:0A:0C:D1:... /CN=VeriSign Class 3 Public Primary Certification Authority - G5
[OT] A1:DB:63:93:91:... /C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority

第一个证书[0]是服务器发送的叶子证书。以下证书 [1] 和 [2] 是服务器发送的链式证书。最后一个证书[OT]是可信根证书,它不是服务器发送的,而是在可信CA的本地存储中。链中的每个证书都由下一个签名,最后一个证书 [OT] 是受信任的,因此信任链是完整的。

如果您通过浏览器(例如使用 NSS 库的 Google Chrome)检查信任链,则会得到以下链:

 [0] 54:7D:B3:AC:BF:... /CN=*.s3.amazonaws.com 
 [1] 5D:EB:8F:33:9E:... /CN=VeriSign Class 3 Secure Server CA - G3
[NT] 4E:B6:D5:78:49:... /CN=VeriSign Class 3 Public Primary Certification Authority - G5

这里 [0] 和 [1] 再次由服务器发送,但 [NT] 是受信任的根证书。虽然这从主题看起来与链证书 [2] 完全一样,但指纹表明证书是不同的。如果您仔细查看证书 [2] 和 [NT],您会看到,证书中的公钥是相同的,因此 [2] 和 [NT] 都可以用于验证 [ 1],因此可用于构建信任链。

这意味着,虽然服务器在所有情况下都发送相同的证书链,但有多种方法可以验证该链直至受信任的根证书。具体如何完成取决于 SSL 库和已知的受信任根证书:

                          [0] (*.s3.amazonaws.com)
                           |
                          [1] (Verisign G3) --------------------------\
                           |                                          |
      /------------------ [2] (Verisign G5 F4:A8:0A:0C:D1...)         |
      |                                                               |
      |              certificates sent by server                      |
 .....|...............................................................|................
      |              locally trusted root certificates                |
      |                                                               |
     [OT] Public Primary Certification Authority        [NT] Verisign G5 4E:B6:D5:78:49
     OpenSSL library                                    Google Chrome (NSS library)

但问题仍然存在,为什么您的验证不成功。 您所做的是将浏览器使用的受信任根证书 (Verisign G5 4E:B6:D5:78:49) 与 OpenSSL 一起使用。但是浏览器(NSS)和 OpenSSL 中的验证工作方式略有不同:

  • NSS:根据服务器发送的证书构建信任链。当我们获得由任何本地受信任的根证书签名的证书时,停止构建链。
  • OpenSSL_ 从服务器发送的证书构建信任链。完成此操作后,检查我们是否有一个受信任的根证书对链中的最新证书进行签名。

由于这种细微的差异,OpenSSL 无法根据根证书 [NT] 验证链 [0]、[1]、[2],因为该证书不签署链 [2] 中的最新元素,而是[1]。如果服务器只发送 [0],[1] 链,则验证将成功。

这是一个long known bug,存在patches,如果最终在OpenSSL 1.0.2 中通过引入X509_V_FLAG_TRUSTED_FIRST 选项来解决这个问题。

【讨论】:

  • 这是一个很好的答案,我已将其标记为正确。谢谢!我的实际错误是传入 cert [2] 并期望它来验证链(也尝试过 [0] 和 [1])。我认为 [2] 是根证书。我不知道要寻找 [OT] 或在哪里找到它。一旦你告诉我指纹,我在 /usr/local/share/certs/ca-root-nss.crt (FreeBSD) 中找到了它,但我也可以在 Firefox 中找到它或从 Verisign 下载它。但是您怎么知道哪个根证书是正确的呢?浏览器是否通过颁发者字符串来识别它们?
  • OpenSSL 按颁发者字符串搜索,然后检查签名是否匹配。