在没有代理的情况下,对某些站点的Python HTTPS请求(urllib2)在Ubuntu 12.04上失败


23

我有一个用Python编写的小应用程序,它曾经可以工作……直到昨天,突然它开始给我HTTPS连接错误。我不记得是否有更新,但是Python 2.7.3rc2和Python 3.2都失败了。

我在Google上进行了搜索,发现当人们在代理背后时会发生这种情况,但是我却没有(自从上次工作以来,我的网络没有任何变化)。我的系统的运行Windows和Python 2.7.2的计算机没有问题(在同一网络中)。

>>> url = 'https://www.mediafire.com/api/user/get_session_token.php'
>>> response = urllib2.urlopen(url).read()
  File "/usr/lib/python2.7/urllib2.py", line 126, in urlopen
    return _opener.open(url, data, timeout)
  File "/usr/lib/python2.7/urllib2.py", line 400, in open
    response = self._open(req, data)
  File "/usr/lib/python2.7/urllib2.py", line 418, in _open
    '_open', req)
  File "/usr/lib/python2.7/urllib2.py", line 378, in _call_chain
    result = func(*args)
  File "/usr/lib/python2.7/urllib2.py", line 1215, in https_open
    return self.do_open(httplib.HTTPSConnection, req)
  File "/usr/lib/python2.7/urllib2.py", line 1177, in do_open
    raise URLError(err)
urllib2.URLError: <urlopen error [Errno 8] _ssl.c:504: EOF occurred in violation of protocol>

怎么了?任何帮助表示赞赏。

PS .:较旧的python版本也不起作用,在我的系统中和USB的实时会话中均不起作用,但请在Ubuntu 11.10实时会话中起作用。


1
您尝试联系的每个SSL站点都会发生这种情况,还是只是一个?如果不是每个站点都出现这种情况,那么您能告诉我们是哪个站点引起了该问题吗?
詹姆斯·亨斯特里奇

好吧,我自己不是一个经验丰富的程序员,我正在尝试从网站的API中读取页面,而这是唯一需要SSL的调用,因此我不知道我一开始是否正确。我一直像普通的urllib.urlopen(url).read()一样使用它,并且它正在工作。您能给我另一个网站的地址或可以回答这个问题的python脚本吗?
巴勃罗(Pablo)

哦,我忘了提:网站是Mediafire。导致问题的原因是其get_session_token调用。
巴勃罗(Pablo)2012年

我能够在该网站上重现此内容。我们已经更新您的问题,以包括相关站点。我怀疑这是OpenSSL的问题,因为wget也会失败。
詹姆斯·亨斯特里奇

在撰写本文时,发生在stream.twitter.com上。
MarkR

Answers:


15

这似乎与在12.04中找到的OpenSSL版本增加了TLS 1.1和1.2支持有关。可以使用OpenSSL命令行工具重现连接失败:

$ openssl s_client -connect www.mediafire.com:443
CONNECTED(00000003)
140491065808544:error:140790E5:SSL routines:SSL23_WRITE:ssl handshake failure:s23_lib.c:177:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 0 bytes and written 320 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
---

如果我强制连接对TLS 1.0使用TLS 1.0,则连接成功 -tls1命令行参数。

我建议您在此处提交有关此问题的错误报告:

https://bugs.launchpad.net/ubuntu/+filebug


2
谢谢!我报告了一个错误。请查看是否可以向其中添加任何相关信息:bugs.launchpad.net/ubuntu/+source/openssl/+bug/965371
Pablo

1
这如何帮助他解决Python中的问题?
Cerin 2013年

2
@Cerin:它将问题隔离为OpenSSL错误而不是Python中的错误,并指示他使用错误跟踪器。此问题已得到解决。
James Henstridge

12

对于像我这样的python新手来说,这是覆盖httplib的最简单方法。在python脚本的顶部,包括以下几行:


import httplib
from httplib import HTTPConnection, HTTPS_PORT
import ssl

class HTTPSConnection(HTTPConnection):
    "This class allows communication via SSL."
    default_port = HTTPS_PORT

    def __init__(self, host, port=None, key_file=None, cert_file=None,
            strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
            source_address=None):
        HTTPConnection.__init__(self, host, port, strict, timeout,
                source_address)
        self.key_file = key_file
        self.cert_file = cert_file

    def connect(self):
        "Connect to a host on a given (SSL) port."
        sock = socket.create_connection((self.host, self.port),
                self.timeout, self.source_address)
        if self._tunnel_host:
            self.sock = sock
            self._tunnel()
        # this is the only line we modified from the httplib.py file
        # we added the ssl_version variable
        self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=ssl.PROTOCOL_TLSv1)

#now we override the one in httplib
httplib.HTTPSConnection = HTTPSConnection
# ssl_version corrections are done

从这里开始,您可以像通常一样使用urllib或任何您使用的东西。

注意:这适用于python 2.7。对于python 3.x解决方案,您需要覆盖http.client中的HTTPSConnection类。我把它留给读者作为练习。:-)


2
我真的很喜欢这个解决方案,它避免了修改任何系统库或其他黑客程序。
MarkR

4
在Ubuntu 12.04上无法使用Python 2.7.4:NameError:未定义名称'socket'。---您还需要添加“导入套接字”。
Ben Walther

在Ubuntu 13.04上运行良好。谢谢!
dharmatech

2
没有理由只打补丁httplib。人们可能会使用其他SSL套接字。可以ssl像下面我的回答中那样修补。
temoto 2013年

这给了我错误BadStatusLine: ''
Cerin 2013年

8

您可以通过修改HTTPSConnection对象来避免修改httplib.py文件:

import httplib, ssl, socket

conn = httplib.HTTPSConnection(URL.hostname)
sock = socket.create_connection((conn.host, conn.port), conn.timeout, conn.source_address)
conn.sock = ssl.wrap_socket(sock, conn.key_file, conn.cert_file, ssl_version=ssl.PROTOCOL_TLSv1)
conn.request('POST', URL.path + URL.query)

仅当未定义connection.sock时,request方法才会创建一个新的套接字。创建自己的添加ssl_version参数将使请求方法使用它。然后,其他所有内容将照常运行。

我遇到了同样的问题,这对我有用。

问候


7

问题在于ssl,它与HTTP无关,因此,httplib如果可以进行修补,为什么要进行修补ssl。对于Python 2.6+,以下代码应修复所有SSL套接字,包括但不限于HTTPS(内置ssl,未尝试使用pyopenssl)。

import functools
import ssl

old_init = ssl.SSLSocket.__init__

@functools.wraps(old_init)
def ubuntu_openssl_bug_965371(self, *args, **kwargs):
  kwargs['ssl_version'] = ssl.PROTOCOL_TLSv1
  old_init(self, *args, **kwargs)

ssl.SSLSocket.__init__ = ubuntu_openssl_bug_965371

好答案。解决问题的好方法。
chnrxn 2015年

3

编辑httplib.py(在Linux上为/usr/lib/pythonX.X/httplib.py)

FIND HTTPSConnection类声明

  class HTTPSConnection(HTTPConnection):
....

内部类代码CHANGE行

self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file)

self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=ssl.PROTOCOL_TLSv1)

然后httplib HTTPS请求应该工作

import httplib
from urlparse import urlparse
url = XXX
URL = urlparse(url)
connection = httplib.HTTPSConnection(URL.hostname)
connection.request('POST', URL.path + URL.query)
response = connection.getresponse()

3
这样编辑系统文件确实不正确。取而代之的是,重新定义了需要通过重新定义它们被改变,任何定义你的代码。
恢复莫妮卡-ζ-2013年

2

此问题可能是由于在Web服务器上禁用了SSLv2引起的,但是Python 2.x默认情况下会尝试与PROTOCOL_SSLv23建立连接。

这是我对堆栈溢出类似问题的回答的链接-https: //stackoverflow.com/a/24166498/41957

更新:这在功能上与上述@temoto的答案相同。


TypeError:未绑定方法__init __()必须以SSLSocket实例作为第一个参数调用(取而代之的是_socketobject实例)
sureshvv 2015年

嗯,partial()不适用于类方法。很快就会发布更好的解决方案。
chnrxn

@sureshvv,如果您可以帮助检查解决方案,将不胜感激。
chnrxn 2015年

@temeto的答案有效。
sureshvv

1

一个对我有用的简单修复方法是覆盖SSL的默认协议:

import ssl
ssl.PROTOCOL_SSLv23 = ssl.PROTOCOL_TLSv1

它有些骇人听闻,但在当今的环境下效果很好。自从发现狮子狗漏洞以来,TLSv1几乎成为Internet上唯一可接受的版本。
chnrxn 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.