Las solicitudes HTTPS de Python (urllib2) a algunos sitios fallan en Ubuntu 12.04 sin proxy

23

Tengo una pequeña aplicación que escribí en Python y solía funcionar ... hasta ayer, cuando de repente comenzó a darme un error en una conexión HTTPS. No recuerdo si hubo una actualización, pero tanto Python 2.7.3rc2 como Python 3.2 están fallando de la misma manera.

Lo busqué en Google y descubrí que esto sucede cuando las personas están detrás de un proxy, pero yo no (y nada ha cambiado en mi red desde la última vez que funcionó). La computadora de mi syster que ejecuta Windows y Python 2.7.2 no tiene problemas (en la misma red).

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

Que pasa Cualquier ayuda es apreciada.

PD .: Las versiones antiguas de python tampoco funcionan, ni en mi sistema ni en una sesión en vivo desde USB, pero sí funcionan en una sesión en vivo de Ubuntu 11.10.

Pablo
fuente
1
¿Ocurre para cada sitio SSL con el que intentas contactar, o solo para el? Si no ocurre para todos los sitios, ¿podría decirnos qué sitio está causando el problema?
James Henstridge
Bueno, yo tampoco soy un programador experimentado, y estoy tratando de leer una página de la API de un sitio, y esa es la única llamada que requiere SSL, así que no sé si lo estaba haciendo bien en primer lugar . Lo he estado usando como una llamada normal urllib.urlopen (url) .read () y estaba funcionando. ¿Podría darme la dirección de otro sitio o un script de Python que responda a esta pregunta?
Pablo
Oh, olvidé mencionar: el sitio es Mediafire. Es su llamada get_session_token la que está causando el problema.
Pablo
Pude reproducir esto con ese sitio. He actualizado su pregunta para incluir el sitio en cuestión. Sospecho que esto es un problema con OpenSSL, ya que wget también falla.
James Henstridge
Esto sucede con stream.twitter.com para mí al momento de escribir.
MarkR

Respuestas:

15

Esto parece estar relacionado con la adición de compatibilidad con TLS 1.1 y 1.2 a la versión de OpenSSL que se encuentra en 12.04. El error de conexión se puede reproducir con la herramienta de línea de comandos 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 conexión tiene éxito si fuerzo la conexión a usar TLS 1.0 con el -tls1argumento de la línea de comando.

Le sugiero que presente un informe de error sobre este problema aquí:

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

James Henstridge
fuente
2
¡Gracias! Informé un error. Por favor, vea si puede agregarle información relevante: bugs.launchpad.net/ubuntu/+source/openssl/+bug/965371
Pablo
1
¿Cómo le ayuda esto a solucionar el problema en Python?
Cerin
2
@Cerin: aisló el problema como un error de OpenSSL en lugar de algo en Python, y le indicó que usara el rastreador de errores. Ese problema ya se ha solucionado.
James Henstridge
12

Para los principiantes de Python como yo, esta es la forma de anular httplib de la manera más fácil. En la parte superior de su secuencia de comandos de Python, incluya estas líneas:


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

De aquí en adelante, puede usar urllib o lo que sea que use como lo haría normalmente.

Nota: Esto es para Python 2.7. Para una solución python 3.x, debe anular la clase HTTPSConnection que se encuentra en http.client. Lo dejo como ejercicio para el lector. :-)

Jeff Mikels
fuente
2
Realmente me gusta esta solución, evita modificar las bibliotecas del sistema u otros hackers.
MarkR
44
No se puede utilizar Python 2.7.4 en Ubuntu 12.04: NameError: el nombre 'socket' no está definido. --- También necesitará agregar "socket de importación".
Ben Walther
Funciona muy bien en Ubuntu 13.04. ¡Gracias!
dharmatech
2
No hay razón para solo parchear httplib. Las personas pueden usar otros sockets SSL. Uno podría parchear en su ssllugar como en mi respuesta a continuación.
temoto
Esto me da el errorBadStatusLine: ''
Cerin
8

Puede evitar modificar el archivo httplib.py modificando su objeto 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)

El método de solicitud crea un nuevo socket solo si connection.sock no está definido. Si crea el suyo propio y agrega el parámetro ssl_version, el método de solicitud lo usará. Entonces todo lo demás funciona como de costumbre.

Estaba teniendo el mismo problema y esto funciona para mí.

Saludos

Adrikrun
fuente
7

El problema está en sslque no tiene nada que ver con HTTP, entonces, ¿por qué parchear httplibsi puede parchar ssl? El siguiente código debería corregir todos los sockets SSL, incluidos, entre otros, HTTPS, para Python 2.6+ (integrado ssl, no lo probé 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
fuente
Buena respuesta. Una forma agradable y elegante de resolver el problema.
chnrxn
3

EDITAR httplib.py (/usr/lib/pythonX.X/httplib.py en Linux)

FIND HTTPS Declaración de clase de conexión

  class HTTPSConnection(HTTPConnection):
....

Línea de CAMBIO de código de clase interior

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

A

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

Entonces la solicitud httplib HTTPS debería funcionar

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
fuente
3
Realmente no es correcto editar un archivo de sistema como ese. En cambio, redefina las definiciones que deban cambiarse, redefiniéndolas en su código.
Restablece a Monica - ζ--
2

Es probable que este problema se deba a que SSLv2 está deshabilitado en el servidor web, pero Python 2.x intenta establecer una conexión con PROTOCOL_SSLv23 de forma predeterminada.

Aquí está el enlace a mi respuesta para un problema similar en Stack Overflow: /programming//a/24166498/41957

Actualización: esto es funcionalmente lo mismo que la respuesta de @ temoto anterior.

chnrxn
fuente
TypeError: el método independiente __init __ () se debe invocar con la instancia SSLSocket como primer argumento (en su lugar se obtuvo la instancia _socketobject)
sureshvv
Hmm, parcial () no funciona para los métodos de clase. Publicaremos una mejor solución en breve.
chnrxn
@sureshvv, si puede ayudar a verificar la solución, será apreciado.
chnrxn
La respuesta de @temeto funcionó.
sureshvv
1

Una solución simple que funcionó para mí fue anular el protocolo predeterminado de SSL:

import ssl
ssl.PROTOCOL_SSLv23 = ssl.PROTOCOL_TLSv1
monis
fuente
Es hack, pero funciona bastante bien en el contexto actual. Desde que se descubrió la vulnerabilidad del caniche, TLSv1 se convirtió prácticamente en la única versión aceptable en Internet.
chnrxn