Les requêtes HTTPS Python (urllib2) sur certains sites échouent sous Ubuntu 12.04 sans proxy

23

J'ai une petite application que j'ai écrite en Python et qui fonctionnait jadis ... 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 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ème sous Windows et Python 2.7.2 n’a aucun problème (sur le même réseau).

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

    
posée Pablo 26.03.2012 - 05:24
la source

7 réponses

15

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 la 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 aboutit si je force la connexion à utiliser TLS 1.0 avec l'argument de ligne de commande -tls1 .

Je vous suggère de déposer un rapport de bogue sur ce problème ici:

lien

    
réponse donnée James Henstridge 26.03.2012 - 08:58
la source
12

Pour les novices en python 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

À partir de maintenant, vous pouvez utiliser urllib ou ce que vous utilisez comme vous le feriez normalement.

Note: Ceci est pour Python 2.7. Pour une solution 3.x python, vous devez remplacer la classe HTTPSConnection située dans http.client. Je laisse cela comme un exercice pour le lecteur. : -)

    
réponse donnée Jeff Mikels 13.03.2013 - 18:25
la source
8

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

    
réponse donnée Adrikrun 13.01.2013 - 01:45
la source
7

Le problème est dans ssl , cela n'a rien à voir avec HTTP, alors pourquoi patcher httplib si vous pouvez patcher ssl . Le code suivant devrait corriger tous les sockets SSL, y compris, mais sans s'y limiter, HTTPS, pour Python 2.6+ (intégré dans ssl , n'a pas essayé avec 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
    
réponse donnée temoto 16.09.2013 - 10:22
la source
3

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)

TO

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

Alors 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()
    
réponse donnée Yagger 14.09.2012 - 13:34
la source
2

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 concernant le dépassement de pile - lien

.

Mise à jour: cela correspond fonctionnellement à la réponse de @ temoto ci-dessus.

    
réponse donnée chnrxn 14.06.2014 - 19:20
la source
1

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
    
réponse donnée monis 26.06.2014 - 05:32
la source

Lire d'autres questions sur les étiquettes