Python-HTTPS-Anfragen (urllib2) an einige Sites schlagen unter Ubuntu 12.04 ohne Proxy fehl

23

Ich habe eine kleine App, die ich in Python geschrieben habe, und sie funktionierte früher ... bis gestern, als plötzlich ein Fehler in einer HTTPS-Verbindung auftrat. Ich erinnere mich nicht, ob es ein Update gab, aber sowohl Python 2.7.3rc2 als auch Python 3.2 schlagen genauso fehl.

Ich habe es gegoogelt und festgestellt, dass dies passiert, wenn sich Personen hinter einem Proxy befinden, aber ich nicht (und nichts hat sich in meinem Netzwerk geändert, seitdem es das letzte Mal funktioniert hat). Der Computer meines Systers, auf dem Windows und Python 2.7.2 ausgeführt werden, weist keine Probleme auf (im selben Netzwerk).

>>> 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>

Was ist falsch? Jede Hilfe wird geschätzt.

PS .: Ältere Python-Versionen funktionieren auch nicht, nicht in meinem System und nicht in einer Live-Sitzung von USB, sondern in einer Ubuntu 11.10-Live-Sitzung.

Pablo
quelle
1
Passiert das für jede SSL-Site, die Sie kontaktieren möchten, oder nur für die eine? Wenn es nicht bei jeder Site vorkommt, können Sie uns dann mitteilen, welche Site das Problem verursacht?
James Henstridge
Nun, ich bin selbst kein erfahrener Programmierer und versuche, eine Seite aus der API einer Site zu lesen. Dies ist der einzige Aufruf, für den SSL erforderlich ist. Daher weiß ich nicht, ob ich es überhaupt richtig gemacht habe . Ich habe es wie einen normalen urllib.urlopen (url) .read () -Aufruf verwendet und es hat funktioniert. Könnten Sie mir bitte die Adresse einer anderen Site oder ein Python-Skript geben, das diese Frage beantworten würde?
Pablo
Oh, ich habe vergessen zu erwähnen: Die Seite ist Mediafire. Es ist der get_session_token-Aufruf, der das Problem verursacht.
Pablo
Ich konnte dies mit dieser Seite reproduzieren. Ich habe Ihre Frage so aktualisiert, dass sie die betreffende Website enthält. Ich vermute, dass dies ein Problem mit OpenSSL ist, da wget ebenfalls ausfällt.
James Henstridge
Dies geschieht bei mir zum Zeitpunkt des Schreibens mit stream.twitter.com.
MarkR

Antworten:

15

Dies scheint mit dem Hinzufügen von TLS 1.1- und 1.2-Unterstützung zu der Version von OpenSSL in Verbindung zu stehen, die in 12.04 gefunden wurde. Der Verbindungsfehler kann mit dem OpenSSL-Befehlszeilentool reproduziert werden:

$ 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
---

Die Verbindung ist erfolgreich, wenn ich die Verwendung von TLS 1.0 mit dem -tls1Befehlszeilenargument erzwinge .

Ich würde vorschlagen, dass Sie hier einen Fehlerbericht zu diesem Problem einreichen:

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

James Henstridge
quelle
2
Vielen Dank! Ich habe einen Fehler gemeldet. Bitte sehen Sie nach, ob Sie relevante Informationen hinzufügen können: bugs.launchpad.net/ubuntu/+source/openssl/+bug/965371
Pablo
1
Wie kann er das Problem in Python umgehen?
Cerin
2
@Cerin: Es hat das Problem als OpenSSL-Fehler und nicht als Fehler in Python isoliert und ihn angewiesen, den Bug-Tracker zu verwenden. Dieses Problem wurde inzwischen behoben.
James Henstridge
12

Für Python-Neulinge wie mich ist hier der einfachste Weg, httplib zu überschreiben. Fügen Sie oben in Ihrem Python-Skript die folgenden Zeilen ein:


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

Ab hier können Sie urllib oder was auch immer verwenden, wie Sie es normalerweise tun würden.

Hinweis: Dies ist für Python 2.7. Für eine Python 3.x-Lösung müssen Sie die HTTPSConnection-Klasse in http.client überschreiben. Ich überlasse das dem Leser als Übung. :-)

Jeff Mikels
quelle
2
Ich mag diese Lösung wirklich, sie vermeidet das Modifizieren von Systembibliotheken oder anderen Hacks.
MarkR
4
Schlägt mit Python 2.7.4 unter Ubuntu 12.04 fehl: NameError: name 'socket' ist nicht definiert. --- Sie müssen auch "Import Socket" hinzufügen.
Ben Walther
Funktioniert hervorragend unter Ubuntu 13.04. Vielen Dank!
Dharmatech
2
Es gibt keinen Grund, nur zu patchen httplib. Benutzer können andere SSL-Sockets verwenden. Man könnte sslstattdessen wie in meiner Antwort unten patchen .
Temoto
Dies gibt mir den FehlerBadStatusLine: ''
Cerin
8

Sie können das Ändern der Datei httplib.py vermeiden, indem Sie Ihr HTTPSConnection-Objekt ändern:

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)

Die Anforderungsmethode erstellt nur dann einen neuen Socket, wenn connection.sock nicht definiert ist. Wenn Sie einen eigenen erstellen und den Parameter ssl_version hinzufügen, wird er von der Anforderungsmethode verwendet. Dann funktioniert alles andere wie gewohnt.

Ich hatte das gleiche Problem und das funktioniert für mich.

Grüße

Adrikrun
quelle
7

Das Problem ist in ssl, es hat nichts mit HTTP zu tun hat, also warum das Patchen , httplibwenn Sie Patch kann ssl. Der folgende Code sollte alle SSL-Sockets einschließlich, aber nicht beschränkt auf HTTPS für Python 2.6+ beheben (eingebaut ssl, nicht getestet 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
temoto
quelle
Gute Antwort. Schöne, elegante Art, das Problem zu lösen.
chnrxn
3

EDIT httplib.py (/usr/lib/pythonX.X/httplib.py unter Linux)

FIND HTTPSConnection-Klassendeklaration

  class HTTPSConnection(HTTPConnection):
....

Innerhalb der Zeile mit dem Klassencode CHANGE

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

ZU

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

Dann sollte die HTTPS-Anforderung von httplib funktionieren

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()
Yagger
quelle
3
Es ist wirklich nicht richtig, eine Systemdatei so zu bearbeiten. Definieren Sie stattdessen alle Definitionen neu, die geändert werden müssen, indem Sie sie in Ihrem Code neu definieren.
ζ--
2

Dieses Problem ist wahrscheinlich darauf zurückzuführen, dass SSLv2 auf dem Webserver deaktiviert ist, Python 2.x jedoch standardmäßig versucht, eine Verbindung mit PROTOCOL_SSLv23 herzustellen.

Hier ist der Link zu meiner Antwort auf ein ähnliches Problem bei Stack Overflow - /programming//a/24166498/41957

Update: Funktionell entspricht dies der obigen Antwort von @ temoto.

chnrxn
quelle
TypeError: Die ungebundene Methode __init __ () muss mit der SSLSocket-Instanz als erstes Argument aufgerufen werden (stattdessen die Instanz got _socketobject)
sureshvv
Hmm, partial () funktioniert nicht für Klassenmethoden. Werde in Kürze eine bessere Lösung posten.
chnrxn
@sureshvv, wenn Sie helfen können, die Lösung zu überprüfen, wird es geschätzt.
chnrxn
@ Temetos Antwort hat funktioniert.
sureshvv
1

Ein einfacher Fix, der für mich funktioniert hat, war, das Standardprotokoll von SSL zu überschreiben:

import ssl
ssl.PROTOCOL_SSLv23 = ssl.PROTOCOL_TLSv1
monis
quelle
Es ist hackisch, aber es funktioniert im heutigen Kontext ziemlich gut. Seit die Pudel-Sicherheitslücke entdeckt wurde, ist TLSv1 so ziemlich die einzige akzeptable Version im Internet.
chnrxn