urllib.request.urlopen: SSL: CERTIFICATE_VERIFY_FAILED Ошибка в Windows> = Vista (7/8/10 / Server 2008) в Python> = 3.4 - PullRequest
0 голосов
/ 29 августа 2018

При попытке использовать Python 3 urlopen на многих HTTPS-сайтах на недавних (> = Vista) машинах Windows я получаю сообщение об ошибке «SSL: CERTIFICATE_VERIFY_FAILED» при попытке сделать urllib.request.urlopen на многих сайтах (даже на некоторых сборочных машинах *). 1003 *, но любопытно, что никогда не https://www.microsoft.com/).

>>> import urllib.request
>>> urllib.request.urlopen("https://www.google.com/")
Traceback (most recent call last):
  File "C:\Python35\lib\urllib\request.py", line 1254, in do_open
    h.request(req.get_method(), req.selector, req.data, headers)
  File "C:\Python35\lib\http\client.py", line 1106, in request
    self._send_request(method, url, body, headers)
  File "C:\Python35\lib\http\client.py", line 1151, in _send_request
    self.endheaders(body)
  File "C:\Python35\lib\http\client.py", line 1102, in endheaders
    self._send_output(message_body)
  File "C:\Python35\lib\http\client.py", line 934, in _send_output
    self.send(msg)
  File "C:\Python35\lib\http\client.py", line 877, in send
    self.connect()
  File "C:\Python35\lib\http\client.py", line 1260, in connect
    server_hostname=server_hostname)
  File "C:\Python35\lib\ssl.py", line 377, in wrap_socket
    _context=self)
  File "C:\Python35\lib\ssl.py", line 752, in __init__
    self.do_handshake()
  File "C:\Python35\lib\ssl.py", line 988, in do_handshake
    self._sslobj.do_handshake()
  File "C:\Python35\lib\ssl.py", line 633, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c
:645)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python35\lib\urllib\request.py", line 163, in urlopen
    return opener.open(url, data, timeout)
  File "C:\Python35\lib\urllib\request.py", line 466, in open
    response = self._open(req, data)
  File "C:\Python35\lib\urllib\request.py", line 484, in _open
    '_open', req)
  File "C:\Python35\lib\urllib\request.py", line 444, in _call_chain
    result = func(*args)
  File "C:\Python35\lib\urllib\request.py", line 1297, in https_open
    context=self._context, check_hostname=self._check_hostname)
  File "C:\Python35\lib\urllib\request.py", line 1256, in do_open
    raise URLError(err)
urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certifica
te verify failed (_ssl.c:645)>

Самое ужасное, что это происходит почти только на серверах build / CI, и часто эти ошибки исчезают после попытки исследовать проблему (например, проверка подключения к данному сайту, который корректно реагирует при попытке через браузер):

>>> import urllib.request
>>> urllib.request.urlopen("https://www.google.com/")
<http.client.HTTPResponse object at 0x0000000002D930B8>

Я слышал много предложений об отключении проверки сертификата путём переписки с контекстами SSL, но я бы хотел этого избежать - я хочу сохранить свою безопасность HTTPS без изменений!

В чем может быть причина этой проблемы? Как я могу это исправить?

1 Ответ

0 голосов
/ 29 августа 2018

К сожалению, это печальная история до сих пор без счастливого конца, подробно изложенная в https://bugs.python.org/issue20916.

Python 3.3 добавил параметр cadefault к urllib.request.urlopen, по умолчанию True (https://bugs.python.org/issue14780),, который заставил запросы HTTPS проверять сертификаты сервера, используя хранилище системных сертификатов по умолчанию.

Python 3.4 сделал SSLContext.set_default_verify_paths своего рода работу в Windows (https://bugs.python.org/issue19292),, позволяющая Python использовать хранилище сертификатов Windows.

Ранее Microsoft отправляла обновления корневых сертификатов через Центр обновления Windows, что обеспечивало постоянное обновление хранилища корневых сертификатов системы (до тех пор, пока пользователь устанавливал обновления). Пока все хорошо.

Однако, начиная с Windows Vista, Windows поставляется с несколькими «основными» сертификатами в хранилище (менее 20, IIRC), и каждый раз, когда CryptoAPI запрашивается проверка сертификата, для которого он не может найти доверенный корень в локальное хранилище, серверы Microsoft связываются, чтобы проверить, есть ли у они доверенный корень для этого. В этом случае корневой сертификат предоставляется и автоматически устанавливается в хранилище системных сертификатов.

К сожалению, Python не использует Windows CryptoAPI, поэтому он не может воспользоваться этим автоматическим механизмом; вместо этого он запрашивает все сертификаты в системном хранилище сертификатов и пытается использовать их - но это означает, что все, что он получает, - это горстка сертификатов, поставляемых с Windows, сертификаты, установленные вручную, плюс все сертификаты, которые случайно было установлено автоматически , обычно при работе в Интернете с помощью Internet Explorer или Edge.

Это делает проблему особенно коварной, поскольку сайты, на которых обнаружена проблема, будут различаться на разных машинах (в основном в зависимости от их истории посещений !) И вообще исчезнут (для этого сайта и для всех остальных). сайтов в зависимости от того же корневого сертификата), если вы проверите, можете ли вы подключиться к сайту через браузер с помощью Windows CryptoAPI. По этой причине новые установки Windows, сборочные машины и серверы (которые не видят большого количества интерактивного просмотра в Интернете) особенно подвержены этой проблеме, в то время как разработчики могут никогда не столкнуться с этой проблемой на своих «обычных» настольных компьютерах.


Как это исправить? К сожалению, простого решения не существует.

  • для простых случаев, таких как CI-сервер, где некоторым тестам требуется доступ к некоторым конкретным доменам, которые практически никогда не меняются, тривиальным обходным путем может быть открытие Internet Explorer и открытие страницы в таких доменах. Это заставит его извлечь необходимый корневой сертификат в локальное хранилище сертификатов, и у Python не будет проблем с ним, пока не истечет срок его действия (обратите внимание: мы говорим о root сертификате, который обычно имеет продолжительность многих лет);
  • вы можете отключить проверку сертификата tout-court ; об этом уже говорилось во многих различных ответах, , например, . Однако, как правило, это нежелательно, поскольку вы отказываетесь от защиты MITM, предоставляемой SSL;
  • вы можете вручную установить все доверенные корневые сертификаты в хранилище сертификатов Windows; вот сайт, который объясняет, как (отказ от ответственности: объясненная процедура выглядит разумной, но я никогда не пробовал ее); к сожалению, это ручная процедура, и вам придется периодически повторять ее, чтобы убедиться, что вы получите новые корневые сертификаты;
  • вы можете установить пакет certifi, который предоставляет собственное хранилище сертификатов (IIRC - это копия хранилища сертификатов Mozilla); затем вы можете использовать его так:

    import certifi
    import urllib.request
    r = urllib.request.urlopen(url_website, cafile=certifi.where())
    

    Это путь, по которому идет популярный модуль requests, который действительно обычно работает "из коробки"; к сожалению, это еще одно хранилище сертификатов, которое необходимо постоянно обновлять, поэтому необходимо периодически обновлять пакет certifi с помощью pip или каким бы образом вы его не установили.


Большое спасибо автору этой статьи в блоге , это было первое, что мне удалось найти, которое правильно объяснило эту проблему.

...