SSLError: bad handshake, Python requests

SSLError: bad handshake, Python requests

我正在消耗 Ebay Trading APIs using Ebay python sdk which is eventually sing python-requests 来拨打 API 电话。

一切正常,但最近几天我无法拨打电话。我收到错误:

SSLError: bad handshake: Error([('SSL routines', 'SSL3_GET_SERVER_CERTIFICATE', 'certificate verify failed')],)

这是完整的回溯:

In [9]: response = api.execute('GetSessionID', data)
---------------------------------------------------------------------------
SSLError                                  Traceback (most recent call last)
<ipython-input-9-eb33610c2a7f> in <module>()
----> 1 response = api.execute('GetSessionID', data)

/home/debian/.virtualenvs/myvirtualenv/local/lib/python2.7/site-packages/ebaysdk/connection.pyc in execute(self, verb, data, list_nodes, verb_attrs, files)
    117
    118         self.build_request(verb, data, verb_attrs, files)
--> 119         self.execute_request()
    120
    121         if hasattr(self.response, 'content'):

/home/debian/.virtualenvs/goplaces/local/lib/python2.7/site-packages/ebaysdk/connection.pyc in execute_request(self)
    182             proxies=self.proxies,
    183             timeout=self.timeout,
--> 184             allow_redirects=True
    185         )
    186

/home/debian/.virtualenvs/myvirtualenv/local/lib/python2.7/site-packages/requests/sessions.pyc in send(self, request, **kwargs)
    574
    575         # Send the request
--> 576         r = adapter.send(request, **kwargs)
    577
    578         # Total elapsed time of the request (approximately)

/home/debian/.virtualenvs/myvirtualenv/local/lib/python2.7/site-packages/requests/adapters.pyc in send(self, request, stream, timeout, verify, cert, proxies)
    431         except (_SSLError, _HTTPError) as e:
    432             if isinstance(e, _SSLError):
--> 433                 raise SSLError(e, request=request)
    434             elif isinstance(e, ReadTimeoutError):
    435                 raise ReadTimeout(e, request=request)

SSLError: bad handshake: Error([('SSL routines', 'SSL3_GET_SERVER_CERTIFICATE', 'certificate verify failed')],)

Whosebug 上有很多相关问题,都是这样说的:

  1. 传递参数 verify=False
  2. 通过CA证书
  3. cacert.pem file 中附加您的 CA 证书(我试过了,没用)

我不能这样做,因为:

  1. 我的虚拟环境中的第三方库正在调用请求。
  2. 从安全角度来看这很糟糕。

此外,

  1. 我可以使用请求在同一 virtualenv 中进行其他 TSL 调用(例如 Amazon marketplace api),这不会导致握手错误或任何其他 SSL 错误。
  2. Ebay SDK 在我的本地系统上运行良好(Mac OsX),问题只出现在我的生产服务器上(Google Cloud/Debian)
  3. chrome 在我的域
  4. 上没有报告 SSL 错误

我不知道为什么会这样。

为什么 SSL routines', 'SSL3_GET_SERVER_CERTIFICATE', 'certificate verify failed' 在我禁用 SSL3 时处于回溯状态。 (我对 SSL 了解不深)。

谢谢!

编辑:

# openssl version
OpenSSL 1.0.2e 3 Dec 2015

根据@Steffen Ullrich 的建议从源代码构建,从 1.0.1 升级到 openssl 1.0.2。

$ pip freeze | grep -i ssl
backports.ssl-match-hostname==3.4.0.2
pyOpenSSL==0.15.1

我猜测这与有关,即OpenSSL底层实现中的多信任路径问题。在那里查看问题的详细信息。

要在不更改受信任的 CA 的情况下解决此问题,您需要有固定的 OpenSSL,即 OpenSSL 1.0.2。或者您可以将一些旧的 CA 证书添加回您的信任库。

  1. pass argument verify=False
  2. pass CA certificate
  3. append you CA certificate in cacert.pem file (I tried this, didn't work)

... This is bad in security point of view.

虽然 verify=False 不利于安全,因为它禁用验证,但其他选项还不错,因为它们只添加额外的信任锚,但保持启用验证。

Why SSL routines', 'SSL3_GET_SERVER_CERTIFICATE', 'certificate verify failed' is in traceback, when I have disabled SSL3.

即使它在那里谈论 SSLv3 也不是那个意思。 TLS 和 SSLv3 共享很多功能,即 TLS 1.0 实际上是 SSL 3.1。因此,OpenSSL 代码中的许多 SSL3_* 函数也与 TLS 1.x 一起使用,这会导致这些恼人的消息。

我认为这与https://pypi.python.org/pypi/certifi/

的这一部分有关

1024-bit Root Certificates

Browsers and certificate authorities have concluded that 1024-bit keys are unacceptably weak for certificates, particularly root certificates. For this reason, Mozilla has removed any weak (i.e. 1024-bit key) certificate from its bundle, replacing it with an equivalent strong (i.e. 2048-bit or greater key) certifiate from the same CA. Because Mozilla removed these certificates from its bundle, certifi removed them as well.

Unfortunately, old versions of OpenSSL (less than 1.0.2) sometimes fail to validate certificate chains that use the strong roots. For this reason, if you fail to validate a certificate using the certifi.where() mechanism, you can intentionally re-add the 1024-bit roots back into your bundle by calling certifi.old_where() instead. This is not recommended in production: if at all possible you should upgrade to a newer OpenSSL. However, if you have no other option, this may work for you.

我的测试是这样的:

root@43b7ec35c240:/usr/src/app# cat /etc/debian_version
8.2
root@43b7ec35c240:/usr/src/app# cat test.py
import logging

import requests

logging.basicConfig()
logging.getLogger().setLevel(logging.DEBUG)
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True

requests.get('https://www.google.com/')
root@43b7ec35c240:/usr/src/app# python test.py
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): www.google.com
DEBUG:requests.packages.urllib3.connectionpool:"GET / HTTP/1.1" 302 263
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): www.google.com.hk
DEBUG:requests.packages.urllib3.connectionpool:"GET /?gfe_rd=cr&ei=XXKbVqqBD8WM8Qe3v7-YAQ HTTP/1.1" 200 None
root@43b7ec35c240:/usr/src/app# openssl version
OpenSSL 1.0.1k 8 Jan 2015
root@43b7ec35c240:/usr/src/app# pip install certifi
Collecting certifi
  Using cached certifi-2015.11.20.1-py2.py3-none-any.whl
Installing collected packages: certifi
Successfully installed certifi-2015.11.20.1
root@43b7ec35c240:/usr/src/app# python test.py
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): www.google.com
Traceback (most recent call last):
  File "test.py", line 16, in <module>
    requests.get('https://www.google.com/')
  File "/usr/local/lib/python2.7/site-packages/requests/api.py", line 69, in get
    return request('get', url, params=params, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/requests/api.py", line 50, in request
    response = session.request(method=method, url=url, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/requests/sessions.py", line 468, in request
    resp = self.send(prep, **send_kwargs)
  File "/usr/local/lib/python2.7/site-packages/requests/sessions.py", line 576, in send
    r = adapter.send(request, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/requests/adapters.py", line 433, in send
    raise SSLError(e, request=request)
requests.exceptions.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:590)
root@43b7ec35c240:/usr/src/app# pip uninstall certifi
Uninstalling certifi-2015.11.20.1:
  /usr/local/lib/python2.7/site-packages/certifi-2015.11.20.1.dist-info/DESCRIPTION.rst
  /usr/local/lib/python2.7/site-packages/certifi-2015.11.20.1.dist-info/METADATA
  /usr/local/lib/python2.7/site-packages/certifi-2015.11.20.1.dist-info/RECORD
  /usr/local/lib/python2.7/site-packages/certifi-2015.11.20.1.dist-info/WHEEL
  /usr/local/lib/python2.7/site-packages/certifi-2015.11.20.1.dist-info/metadata.json
  /usr/local/lib/python2.7/site-packages/certifi-2015.11.20.1.dist-info/top_level.txt
  /usr/local/lib/python2.7/site-packages/certifi/__init__.py
  /usr/local/lib/python2.7/site-packages/certifi/__init__.pyc
  /usr/local/lib/python2.7/site-packages/certifi/__main__.py
  /usr/local/lib/python2.7/site-packages/certifi/__main__.pyc
  /usr/local/lib/python2.7/site-packages/certifi/cacert.pem
  /usr/local/lib/python2.7/site-packages/certifi/core.py
  /usr/local/lib/python2.7/site-packages/certifi/core.pyc
  /usr/local/lib/python2.7/site-packages/certifi/old_root.pem
  /usr/local/lib/python2.7/site-packages/certifi/weak.pem
Proceed (y/n)? y
  Successfully uninstalled certifi-2015.11.20.1
root@43b7ec35c240:/usr/src/app# python test.py
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): www.google.com
DEBUG:requests.packages.urllib3.connectionpool:"GET / HTTP/1.1" 302 263
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): www.google.com.hk
DEBUG:requests.packages.urllib3.connectionpool:"GET /?gfe_rd=cr&ei=AnObVtG4BcGM8QeC6r-YCw HTTP/1.1" 200 None

然后我尝试通过以下方式更新我的 libssl 包:

# curl http://apt.wikimedia.org/autoinstall/keyring/wikimedia-archive-keyring.gpg | apt-key add -

# echo "deb http://apt.wikimedia.org/wikimedia jessie-wikimedia backports" >> /etc/apt/sources.list

# apt-get update
# apt-get install libssl1.0.0

# openssl version
OpenSSL 1.0.1k 8 Jan 2015 (Library: OpenSSL 1.0.2e 3 Dec 2015)

之后,即使安装了certifi,我也很好。

# pip freeze |grep certifi
certifi==2015.11.20.1

# python test.py
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): www.google.com
DEBUG:requests.packages.urllib3.connectionpool:"GET / HTTP/1.1" 302 263
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): www.google.com.hk
DEBUG:requests.packages.urllib3.connectionpool:"GET /?gfe_rd=cr&ei=4XSbVt2gK8OM8Qe1tICIBA HTTP/1.1" 200 None