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

23

J'ai une petite application que j'ai écrite en Python et elle 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 tout de même.

Je l'ai googlé et j'ai découvert que cela se produit lorsque les gens sont 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 exécutant 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, ni dans mon système ni dans une session en direct à partir d'USB, mais fonctionnent dans une session en direct d'Ubuntu 11.10.

Pablo
la source
1
Cela se produit-il pour chaque site SSL que vous essayez de contacter, ou seulement pour celui-ci? Si cela ne se produit pas pour tous les sites, pourriez-vous nous dire quel site est à l'origine du problème?
James Henstridge
Eh bien, je ne suis pas moi-même un programmeur expérimenté, et j'essaie de lire une page de l'API d'un site, et c'est le seul appel qui nécessite SSL, donc je ne sais pas si je le faisais bien en premier lieu . Je l'ai utilisé comme un appel normal à urllib.urlopen (url) .read () et cela fonctionnait. Pourriez-vous s'il vous plaît me donner l'adresse d'un autre site ou un script python qui répondrait à cette question?
Pablo
Oh, j'ai oublié de mentionner: le site est Mediafire. C'est son appel get_session_token qui cause le problème.
Pablo
J'ai pu reproduire cela avec ce site. J'ai mis à jour votre question pour inclure le site en question. Je soupçonne que c'est un problème avec OpenSSL, car wget échoue également.
James Henstridge
Cela se produit avec stream.twitter.com pour moi au moment de la rédaction.
MarkR

Réponses:

15

Cela semble être lié à l'ajout de la prise en charge TLS 1.1 et 1.2 à la version d'OpenSSL trouvée 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' -tls1argument de ligne de commande.

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

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

James Henstridge
la source
2
Merci! J'ai signalé un bug. Veuillez voir si vous pouvez y ajouter des informations pertinentes: bugs.launchpad.net/ubuntu/+source/openssl/+bug/965371
Pablo
1
Comment cela l'aide-t-il à contourner le problème en Python?
Cerin
2
@Cerin: il a isolé le problème en tant que bogue OpenSSL plutôt qu'en quelque chose en Python, et lui a demandé d'utiliser le suivi des bogues. Ce problème a depuis été corrigé.
James Henstridge
12

Pour les novices en python comme moi, voici la façon de remplacer httplib de la manière la plus simple. 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 là, vous pouvez utiliser urllib ou tout ce que vous utilisez comme vous le feriez normalement.

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

Jeff Mikels
la source
2
J'aime vraiment cette solution, elle évite de modifier toute bibliothèque système ou autre piratage.
MarkR
4
Échec de l'utilisation de Python 2.7.4 sur Ubuntu 12.04: NameError: le nom 'socket' n'est pas défini. --- Vous devrez également ajouter "import socket".
Ben Walther
Fonctionne très bien sur Ubuntu 13.04. Merci!
dharmatech
2
Il n'y a aucune raison de ne patcher que httplib. Les gens peuvent utiliser d'autres sockets SSL. On pourrait sslplutôt corriger comme dans ma réponse ci-dessous.
temoto
Cela me donne l'erreurBadStatusLine: ''
Cerin
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 de demande 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 demande l'utilisera. Ensuite, tout le reste fonctionne comme d'habitude.

J'avais le même problème et cela fonctionne pour moi.

Cordialement

Adrikrun
la source
7

Le problème est ssl, il n'a rien à voir avec HTTP, alors pourquoi patcher httplibsi 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é 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
temoto
la source
Bonne réponse. Belle façon élégante de résoudre le problème.
chnrxn
3

MODIFIER httplib.py (/usr/lib/pythonX.X/httplib.py sous Linux)

FIND HTTPSConnection, déclaration de classe

  class HTTPSConnection(HTTPConnection):
....

Code de classe interne CHANGE line

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)

La demande HTTPS httplib 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()
Yagger
la source
3
Il n'est vraiment pas juste d'éditer un fichier système comme ça. Au lieu de cela, redéfinissez les définitions qui doivent être modifiées, en les redéfinissant dans votre code.
Rétablir Monica - ζ--
2

Ce problème est probablement dû à la désactivation de SSLv2 sur le serveur Web, mais Python 2.x essaie d'établir une connexion avec PROTOCOL_SSLv23 par défaut.

Voici le lien vers ma réponse pour un problème similaire sur Stack Overflow - /programming//a/24166498/41957

Mise à jour: c'est fonctionnellement la même chose que la réponse de @ temoto ci-dessus.

chnrxn
la source
TypeError: la méthode non liée __init __ () doit être appelée avec l'instance SSLSocket comme premier argument (obtenu l'instance _socketobject à la place)
sureshvv
Hmm, partial () ne fonctionne pas pour les méthodes de classe. Publiera bientôt une meilleure solution.
chnrxn
@sureshvv, si vous pouvez aider à vérifier la solution, elle sera appréciée.
chnrxn
La réponse de @ temeto a fonctionné.
sureshvv
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
monis
la source
C'est hackish, mais cela fonctionne plutôt bien dans le contexte actuel. Depuis que la vulnérabilité du caniche a été découverte, TLSv1 est devenu à peu près la seule version acceptable sur Internet.
chnrxn