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


23

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

Я погуглив його і дізнався, що це відбувається, коли люди стоять за проксі-сервером, але я цього немає (і в моїй мережі нічого не змінилося з останнього часу, коли це працювало). У комп'ютера мого систера під керуванням Windows та 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, але DO працюють в режимі живої сесії Ubuntu 11.10.


1
Чи трапляється це з кожним SSL-сайтом, на який ви намагаєтеся зв’язатися, або лише з одним? Якщо це відбувається не для кожного сайту, то чи можете ви сказати нам, який сайт викликає проблему?
James Henstridge

Ну, я сам не досвідчений програміст, і я намагаюся прочитати сторінку з API сайту, і це єдиний дзвінок, для якого потрібен SSL, тому я не знаю, чи робив я це правильно в першу чергу . Я використовував його як звичайний дзвінок urllib.urlopen (url) .read (), і він працював. Скажіть, будь ласка, адресу іншої сторінки чи сценарій пітона, який би відповів на це запитання?
Пабло

О, я забув згадати: на сайті Mediafire. Саме його виклик get_session_token викликає проблему.
Пабло

Мені вдалося відтворити це за допомогою цього сайту. Я оновив ваше запитання, щоб включити відповідний сайт. Я підозрюю, що це проблема з OpenSSL, оскільки wget теж не працює.
Джеймс Генстридж

Це відбувається з 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?
Серін

2
@Cerin: він виокремив проблему як помилку OpenSSL, а не щось у Python, і направив його використовувати трекер помилок. Ця проблема з тих пір виправлена.
James Henstridge

12

Для початківців python, таких як я, ось спосіб перемогти httplib найпростішим способом. Вгорі вашого сценарію python включайте наступні рядки:


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. Для рішення python 3.x вам потрібно перекрити клас HTTPSConnection, знайдений у http.client. Я залишаю це як вправу для читача. :-)


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

4
Не вдалося використовувати Python 2.7.4 на Ubuntu 12.04: NameError: ім'я 'socket' не визначено. --- Вам також потрібно буде додати "імпорт сокета".
Бен Уолтер

Чудово працює на 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)

Метод запиту створює новий сокет лише в тому випадку, якщо з'єднання.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)

ЗНАЙТИ HTTPSЗаявлення про клас з'єднання

  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)

Тоді httpsb 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

Відповідь @ temeto спрацювала.
sureshvv

1

Просте виправлення, яке працювало для мене, полягало в тому, щоб замінити протокол за замовчуванням SSL:

import ssl
ssl.PROTOCOL_SSLv23 = ssl.PROTOCOL_TLSv1

Це хакітство, але воно працює досить добре в сьогоднішньому контексті. З моменту виявлення вразливості пуделя, TLSv1 в значній мірі став єдиною прийнятною версією в Інтернеті.
chnrxn
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.