Запросы Python HTTPS (urllib2) к некоторым сайтам завершаются сбоем в Ubuntu 12.04 без прокси


23

У меня есть небольшое приложение, которое я написал на Python, и оно работало ... до вчерашнего дня, когда оно внезапно начало давать мне ошибку в соединении HTTPS. Я не помню, было ли обновление, но оба Python 2.7.3rc2 и Python 3.2 терпят неудачу точно так же.

Я прогуглил это и обнаружил, что это происходит, когда люди находятся за прокси, но я не являюсь (и ничего не изменилось в моей сети с тех пор, как в последний раз это работало). компьютер моей штатной медсестрой бежит окна и Python 2.7.2 не имеет никаких проблем (в той же сети).

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

В чем дело? Любая помощь приветствуется.

PS: Старые версии Python также не работают, не в моей системе и не во время сеанса в реальном времени с USB, но ДОЛЖНЫ работать в сеансе Ubuntu 11.10.


1
Происходит ли это для каждого SSL сайта вы пытаетесь контакт, или только один? Если это происходит не для каждого сайта, то не могли бы вы рассказать нам, какой сайт вызывает проблему?
Джеймс Henstridge

Ну, я сам не опытный программист, и я пытаюсь прочитать страницу из API сайта, и это единственный вызов, который требует SSL, поэтому я не знаю, правильно ли я это делал в первую очередь , Я использовал его как обычный вызов urllib.urlopen (url) .read (), и он работал. Не могли бы вы дать мне адрес другого сайта или скрипт на python, который бы ответил на этот вопрос?
Pablo

О, я забыл упомянуть: сайт Mediafire. Это его get_session_token вызов , который вызывает проблему.
Пабло

Я был в состоянии воспроизвести это с этим сайтом. Я обновил свой вопрос , чтобы включить сайт в вопросе. Я подозреваю , что это проблема с OpenSSL, так как Wget не может тоже.
Джеймс Henstridge

Это происходит с stream.twitter.com для меня на момент написания статьи.
MarkR

Ответы:


15

Похоже, это связано с добавлением поддержки TLS 1.1 и 1.2 к версии OpenSSL, найденной в 12.04. Ошибка подключения может быть воспроизведена с помощью инструмента командной строки 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
---

Соединение будет успешным, если я заставлю соединение использовать TLS 1.0 с -tls1аргументом командной строки.

Я хотел бы предложить вам подать отчет об ошибке об этой проблеме здесь:

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


2
Спасибо! Я сообщил об ошибке. Пожалуйста, если вы можете добавить любую соответствующую информацию к нему: bugs.launchpad.net/ubuntu/+source/openssl/+bug/965371
Пабло

1
Как это поможет ему обойти эту проблему в Python?
Cerin

2
@Cerin: он выделил проблему как OpenSSL ошибка , а не что - то в Python, и направил его на использование ошибка трекера. Эта проблема была с тех пор исправлена.
Джеймс Хенстридж

12

Для питона новичков вроде меня, вот так, чтобы переопределить HTTPLIB самый простой способ. В верхней части питона сценария, включают в себя следующие строки:


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

С этого момента, вы можете использовать URLLIB или что вы используете так же, как обычно.

Примечание: Это для Python 2.7. Для решения питона 3.x, вам необходимо переопределить класс HTTPSConnection найденный в http.Client. Я оставляю это в качестве упражнения для читателя. :-)


2
Мне очень нравится это решение, он избегает видоизменения каких - либо системных библиотек или других повозка , запряженная волами.
MarkR

4
Не удается с помощью Python 2.7.4 на Ubuntu 12.04: NameError: название «сокет» не определен. --- Вам также нужно добавить «гнездо импорта».
Бен Вальтер,

Отлично работает на Ubuntu 13.04. Благодарность!
dharmatech

2
Там нет никаких оснований только пластырь httplib. Люди могут использовать другие SSL сокеты. Можно было бы исправить , sslа не как в моем ответе ниже.
Темото

Это дает мне ошибкуBadStatusLine: ''
Керин

8

Вы можете избежать изменения файла httplib.py путем изменения объекта 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)

Метод запроса создает новый сокет, только если connection.sock не определен. Создание вашего собственного с добавлением параметра ssl_version заставит метод запроса использовать его. Тогда все остальное работает как обычно.

У меня была та же проблема, и это работает для меня.

С уважением


7

Проблема в том ssl, что это не имеет ничего общего с HTTP, так зачем исправлять, httplibесли можно исправлять ssl. Следующий код должен исправить все сокеты SSL, включая, но не ограничиваясь HTTPS, для Python 2.6+ (встроенный ssl, не пробовал 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

Хороший ответ. Хороший, элегантный способ решить эту проблему.
chnrxn

3

EDIT httplib.py (/usr/lib/pythonX.X/httplib.py на Linux)

НАЙТИ объявление класса HTTPSConnection

  class HTTPSConnection(HTTPConnection):
....

Внутри класса код ИЗМЕНИТЬ линия

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)

Затем HTTPLIB HTTPS запрос должен работать

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

3
Это действительно неправильно редактировать системный файл подобным образом. Вместо этого переопределить любые определения , которые должны быть изменены, путем переопределения их в вашем коде.
Восстановите Моника - ζ--

2

Вероятно, эта проблема связана с отключением SSLv2 на веб-сервере, но Python 2.x пытается установить соединение с PROTOCOL_SSLv23 по умолчанию.

Вот ссылка на мой ответ для аналогичной проблемы переполнения стека - /programming//a/24166498/41957

Обновление: это функционально совпадает с ответом @ temoto выше.


TypeError: несвязанный метод __init __ () должен быть вызван SSLSocket например , в качестве первого аргумента (например , получил _socketobject вместо)
sureshvv

Хм, частичный () не работает для методов класса. Скоро выложу лучшее решение.
chnrxn

@sureshvv, если вы можете помочь проверить решение, оно будет оценено.
chnrxn

Ответ @ Темето сработал.
sureshvv

1

Простое исправление, которое сработало для меня, было переопределить протокол SSL по умолчанию:

import ssl
ssl.PROTOCOL_SSLv23 = ssl.PROTOCOL_TLSv1

Он хакерский, но в современном контексте работает довольно хорошо. С тех пор как пудель обнаружена уязвимость, TLSv1 в значительной степени стал единственным приемлемым вариантом в Интернете.
chnrxn
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.