Solicitações HTTPS do Python (urllib2) para alguns sites falham no Ubuntu 12.04 sem proxy

23

Eu tenho um pequeno aplicativo que escrevi em Python e ele funcionava ... até ontem, quando de repente começou a me dar um erro em uma conexão HTTPS. Não me lembro se houve uma atualização, mas tanto o Python 2.7.3rc2 quanto o Python 3.2 estão falhando da mesma forma.

Pesquisei no Google e descobri que isso acontece quando as pessoas estão atrás de um proxy, mas não estou (e nada mudou na minha rede desde a última vez que funcionou). O computador do meu syster executando o Windows e o Python 2.7.2 não tem problemas (na mesma rede).

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

O que há de errado? Qualquer ajuda é apreciada.

PS: As versões mais antigas de python também não funcionam, nem no meu sistema nem em uma sessão ao vivo a partir do USB, mas funcionam em uma sessão ao vivo no Ubuntu 11.10.

Pablo
fonte
1
Isso acontece para todos os sites SSL com os quais você tenta entrar em contato, ou apenas um? Se isso não ocorrer em todos os sites, você poderia nos dizer qual site está causando o problema?
James Henstridge 26/03
Bem, eu não sou um programador experiente e estou tentando ler uma página da API de um site, e essa é a única chamada que requer SSL, então não sei se estava fazendo certo em primeiro lugar . Eu tenho usado como uma chamada normal urllib.urlopen (url) .read () e estava funcionando. Você poderia me dar o endereço de outro site ou um script python que respondesse a essa pergunta?
26412 Pablo
Ah, esqueci de mencionar: o site é Mediafire. É a chamada get_session_token que está causando o problema.
26412 Pablo
Consegui reproduzir isso com esse site. Atualizei sua pergunta para incluir o site em questão. Eu suspeito que este seja um problema com o OpenSSL, pois o wget também falha.
James Henstridge 26/03
Isso acontece com stream.twitter.com para mim no momento em que escrevo.
MarkR

Respostas:

15

Isso parece estar relacionado à adição do suporte ao TLS 1.1 e 1.2 à versão do OpenSSL encontrada na 12.04. A falha de conexão pode ser reproduzida com a ferramenta de linha de comando 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
---

A conexão terá êxito se forçar a conexão a usar o TLS 1.0 com o -tls1argumento da linha de comando.

Eu sugiro que você envie um relatório de bug sobre esse problema aqui:

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

James Henstridge
fonte
2
Obrigado! Eu relatei um bug. Por favor, veja se você pode adicionar qualquer informação relevante para ele: bugs.launchpad.net/ubuntu/+source/openssl/+bug/965371
Pablo
1
Como isso o ajuda a solucionar o problema no Python?
Cerin 23/09
2
@ Cerin: isolou o problema como um bug do OpenSSL e não como algo em Python, e o instruiu a usar o rastreador de erros. Esse problema já foi corrigido.
James Henstridge 24/13
12

Para iniciantes em python como eu, aqui está a maneira de substituir o updplib da maneira mais fácil. Na parte superior do seu script python, inclua estas linhas:


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 daqui, você pode usar o urllib ou o que quer que use como faria normalmente.

Nota: Isto é para python 2.7. Para uma solução python 3.x, você precisa substituir a classe HTTPSConnection encontrada em http.client. Deixo isso como um exercício para o leitor. :-)

Jeff Mikels
fonte
2
Eu realmente gosto dessa solução, ela evita a modificação de qualquer biblioteca de sistema ou outra invasão.
MarkR
4
Falha ao usar o Python 2.7.4 no Ubuntu 12.04: NameError: name 'socket' não está definido. --- Você precisará adicionar "importar soquete" também.
Ben Walther
Funciona muito bem no Ubuntu 13.04. Obrigado!
dharmatech
2
Não há razão para apenas corrigir httplib. As pessoas podem usar outros soquetes SSL. Pode-se corrigir sslcomo na minha resposta abaixo.
temoto 16/09/13
Isto dá-me o erroBadStatusLine: ''
Cerin
8

Você pode evitar modificar o arquivo httplib.py, modificando seu 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)

O método de solicitação cria um novo soquete somente se connection.sock não estiver definido. Criar o seu próprio adicionando o parâmetro ssl_version fará com que o método de solicitação o use. Então tudo o resto funciona como de costume.

Eu estava tendo o mesmo problema e isso funciona para mim.

Saudações

Adrikrun
fonte
7

O problema está ssl, ele não tem nada a ver com HTTP, então por que aplicar patches httplibse você pode aplicar patches ssl? O código a seguir deve corrigir todos os soquetes SSL, incluindo, mas não limitado a HTTPS, para Python 2.6+ (embutido ssl, não tentei com 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
fonte
Boa resposta. Maneira agradável e elegante de resolver o problema.
Chnrxn
3

EDITDATEPLIFIB.py (/usr/lib/pythonX.X/httplib.py no Linux)

Declaração de classe FIND HTTPSConnection

  class HTTPSConnection(HTTPConnection):
....

Código da classe interna da linha CHANGE

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

PARA

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

Em seguida, a solicitação HTTPS HTPLPS deve 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
fonte
3
Realmente não é correto editar um arquivo de sistema como esse. Em vez disso, redefina todas as definições que precisam ser alteradas, redefinindo-as no seu código.
Reintegrar Monica - ζ--
2

Esse problema provavelmente ocorre porque o SSLv2 está desativado no servidor da Web, mas o Python 2.x tenta estabelecer uma conexão com o PROTOCOL_SSLv23 por padrão.

Aqui está o link para a minha resposta para um problema semelhante no Stack Overflow - /programming//a/24166498/41957

Atualização: é funcionalmente o mesmo que a resposta de @ temoto acima.

chnrxn
fonte
TypeError: Não consolidado __init método __ () deve ser chamado com instância SSLSocket como primeiro argumento (instância _socketobject tem vez)
sureshvv
Hmm, parcial () não funciona para métodos de classe. Irá publicar uma solução melhor em breve.
Chnrxn
@sureshvv, se você puder ajudar a verificar a solução, ela será apreciada.
Chnrxn
A resposta de @ temeto funcionou.
Sureshvv
1

Uma correção simples que funcionou para mim foi substituir o protocolo padrão do SSL:

import ssl
ssl.PROTOCOL_SSLv23 = ssl.PROTOCOL_TLSv1
monis
fonte
É um truque, mas funciona muito bem no contexto de hoje. Desde que a vulnerabilidade do poodle foi descoberta, o TLSv1 praticamente se tornou a única versão aceitável na Internet.
Chnrxn