J'ai une petite application que j'ai écrite dans Python et qui fonctionnait ... jusqu'à hier, quand elle a soudainement commencé à me donner une erreur dans une connexion HTTPS. Je ne me souviens pas s'il y a eu une mise à jour, mais Python 2.7.3rc2 et Python 3.2 échouent de la même manière.
Je l'ai googlé et j'ai découvert que cela se produisait lorsque des personnes se trouvaient derrière un proxy, mais je ne le suis pas (et rien n'a changé dans mon réseau depuis la dernière fois que cela a fonctionné). L'ordinateur de mon systʻeme tournant sous Windows et Python 2.7.2 n'a pas de probleme (sur le meme reseau).
>>> 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>
Qu'est-ce qui ne va pas? Toute aide est appréciée.
PS: Les anciennes versions de python ne fonctionnent pas non plus, pas dans mon système ni dans une session en direct via USB, mais dans une session en direct Ubuntu 11.10.
Cela semble être lié à l’ajout de la prise en charge de TLS 1.1 et 1.2 à la version d’OpenSSL disponible dans 12.04. L'échec de connexion peut être reproduit avec l'outil de ligne de commande 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
---
La connexion réussit si je force la connexion à utiliser TLS 1.0 avec l'argument de ligne de commande -tls1
.
Je suggère que vous déposiez un rapport de bogue sur ce problème ici:
Pour python novices comme moi, voici le moyen le plus simple de remplacer httplib. En haut de votre script python, incluez ces lignes:
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
A partir de là, vous pouvez utiliser urllib ou ce que vous utilisez comme vous le feriez normalement.
Note: Ceci est pour python 2.7. Pour une solution python 3.x, vous devez remplacer la classe HTTPSConnection située dans http.client. Je laisse cela comme un exercice pour le lecteur. :-)
Vous pouvez éviter de modifier le fichier httplib.py en modifiant votre objet HTTPSConnection:
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)
La méthode request crée un nouveau socket uniquement si connection.sock n'est pas défini. Si vous créez le vôtre en ajoutant le paramètre ssl_version, la méthode de requête l’utilisera. Ensuite, tout le reste fonctionne comme d'habitude.
J'avais le même problème et cela fonctionne pour moi.
Cordialement
Le problème est dans ssl
name__, il n’a rien à voir avec HTTP, alors pourquoi patcher httplib
si vous pouvez corriger ssl
name__. Le code suivant devrait corriger tous les sockets SSL, y compris, mais sans s'y limiter, HTTPS, pour Python 2.6+ (construit dans ssl
name__, n'a pas essayé avec pyopenssl
name__).
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
EDIT httplib.py (/usr/lib/pythonX.X/httplib.py sous Linux)
FIND déclaration de classe HTTPSConnection
class HTTPSConnection(HTTPConnection):
....
Ligne de changement de code de classe interne
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)
Ensuite, la requête httplib HTTPS devrait fonctionner
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()
Ce problème est probablement dû à la désactivation de SSLv2 sur le serveur Web, mais Python 2.x tente d'établir une connexion avec PROTOCOL_SSLv23 par défaut.
Voici le lien vers ma réponse à un problème similaire sur Stack Overflow - https://stackoverflow.com/a/24166498/41957
Mise à jour: cela correspond fonctionnellement à la réponse de @ temoto ci-dessus.
Une solution simple qui a fonctionné pour moi a été de remplacer le protocole par défaut de SSL:
import ssl
ssl.PROTOCOL_SSLv23 = ssl.PROTOCOL_TLSv1