Как получить более подробную информацию о request.exceptions.SSLError?

Когда я запрашиваю URL-адрес с просроченным сертификатом HTTPS, я не получаю значимой ошибки от запросов. Вместо этого он дает мне каскад «ssl.SSLError: A failure in the SSL library occurred».

См. этот пример с https://expired.badssl.com/ :

>python
Python 3.9.5 (tags/v3.9.5:0a7dcbd, May  3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> requests.get("https://expired.badssl.com/")
Traceback (most recent call last):
  File "C:\Users\me\apps\Python39\lib\site-packages\urllib3\connectionpool.py", line 670, in urlopen
    httplib_response = self._make_request(
  File "C:\Users\me\apps\Python39\lib\site-packages\urllib3\connectionpool.py", line 381, in _make_request
    self._validate_conn(conn)
  File "C:\Users\me\apps\Python39\lib\site-packages\urllib3\connectionpool.py", line 978, in _validate_conn
    conn.connect()
  File "C:\Users\me\apps\Python39\lib\site-packages\urllib3\connection.py", line 362, in connect
    self.sock = ssl_wrap_socket(
  File "C:\Users\me\apps\Python39\lib\site-packages\urllib3\util\ssl_.py", line 386, in ssl_wrap_socket
    return context.wrap_socket(sock, server_hostname=server_hostname)
  File "C:\Users\me\apps\Python39\lib\ssl.py", line 500, in wrap_socket
    return self.sslsocket_class._create(
  File "C:\Users\me\apps\Python39\lib\ssl.py", line 1040, in _create
    self.do_handshake()
  File "C:\Users\me\apps\Python39\lib\ssl.py", line 1309, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: A failure in the SSL library occurred (_ssl.c:1129)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\me\apps\Python39\lib\site-packages\requests\adapters.py", line 439, in send
    resp = conn.urlopen(
  File "C:\Users\me\apps\Python39\lib\site-packages\urllib3\connectionpool.py", line 726, in urlopen
    retries = retries.increment(
  File "C:\Users\me\apps\Python39\lib\site-packages\urllib3\util\retry.py", line 446, in increment
    raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='expired.badssl.com', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLError(1, 'A failure in the SSL library occurred (_ssl.c:1129)')))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\me\apps\Python39\lib\site-packages\requests\api.py", line 76, in get
    return request('get', url, params=params, **kwargs)
  File "C:\Users\me\apps\Python39\lib\site-packages\requests\api.py", line 61, in request
    return session.request(method=method, url=url, **kwargs)
  File "C:\Users\me\apps\Python39\lib\site-packages\requests\sessions.py", line 530, in request
    resp = self.send(prep, **send_kwargs)
  File "C:\Users\me\apps\Python39\lib\site-packages\requests\sessions.py", line 643, in send
    r = adapter.send(request, **kwargs)
  File "C:\Users\me\apps\Python39\lib\site-packages\requests\adapters.py", line 514, in send
    raise SSLError(e, request=request)
requests.exceptions.SSLError: HTTPSConnectionPool(host='expired.badssl.com', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLError(1, 'A failure in the SSL library occurred (_ssl.c:1129)')))
>>>

Requests — это 2.24.0, а ssl.OPENSSL_VERSION написано «OpenSSL 1.1.1h 22 сентября 2020 года». Не могу обновить пакеты.

Как я могу получить осмысленную ошибку или сообщение об ошибке, в котором говорится, что срок действия сертификата истек?


196
2

Ответы:

Обычно вы справляетесь с такими вещами следующим образом:

import requests
try:
    requests.get("https://expired.badssl.com/")
except requests.exceptions.SSLError as e:
    print(f'oops! got the following SSL error: {e}')

И на моей тестовой машине вывод содержит [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired

Поскольку в вашем тексте исключения нет «сертификата, срок действия которого истек», я подозреваю, что у вас есть ошибка в вашей версии чего-то. requests 2.24.0 не последняя версия. Кроме того, запросы используют как urllib3, так и certifi. Возможно, вам стоит попробовать:

Pip install --upgrade urllib3 requests certifi


Решено

Требования:

  • Среда: Python3.9.5/запросы 2.24.0/OpenSSL 1.1.1h
  • если связь с сервером не удалась, следует определить, не является ли проблема просроченным сертификатом сервера

Самым простым решением было бы обновить установленный OpenSSL, скажем, до текущей версии 1.1.1n, но даже 1.1.1i будет достаточно, чтобы получить сообщение, содержащее certificate has expired. Но обновление библиотеки OpenSSL невозможно, как вы упоминаете в своем посте.

Кроме того, вы можете перехватывать исключения во время запроса, а затем явно проверять, не истек ли срок действия сертификата сервера.

Для этого вы можете, например, взять функцию get_cert_for_hostname из моего предыдущего ответ на переполнение стека и соответственно сравнить поле not_valid_after с текущей датой.

Простой автономный пример может выглядеть примерно так:

import ssl
import requests
import platform
from datetime import datetime
from cryptography import x509


def get_cert_for_hostname(hostname, port):
    conn = ssl.create_connection((hostname, port))
    context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
    sock = context.wrap_socket(conn, server_hostname=hostname)
    certDER = sock.getpeercert(True)
    certPEM = ssl.DER_cert_to_PEM_cert(certDER)
    conn.close()
    return x509.load_pem_x509_certificate(certPEM.encode('ascii'))


def is_cert_expired(hostname, port):
    cert = get_cert_for_hostname(hostname, port)
    return datetime.now() > cert.not_valid_after


if __name__ == '__main__':
    print(f"Python version: {platform.python_version()}")
    print(f"OpenSSL version: {ssl.OPENSSL_VERSION}")
    print(f"requests version: {requests.__version__}")  #
    hosts = ['software7.com', 'expired.badssl.com']
    for host in hosts:
        try:
            requests.get(f"https://{host}")
            print(f"request for host {host} was successful")
        except BaseException as err:
            if is_cert_expired(host, 443):
                print(f"certificate for {host} expired")
            else:
                print(f"error {err} with {host}")

Затем в консоль отладки будет записано следующее:

Python version: 3.9.5
OpenSSL version: OpenSSL 1.1.1h  22 Sep 2020
requests version: 2.24.0
request for host software7.com was successful
certificate for expired.badssl.com expired

Интересные вопросы для изучения

Группировка нескольких элементов в списке в несколько групп разного размера pythonAttributeError: объект «Серия» не имеет атрибута «нижний» PandasКак учитывать дату и время при обнаружении выбросов с помощью pandas в PythonЯ хочу, чтобы мой скрипт Python отправлял электронные письма 1 за 1 из списка адресов электронной почты (например, 1-е выполнено, затем повторите задачу и перейдите ко второму, пока не закончите)Сравнить два значения столбца dataframe и присоединиться к условию в python?Должен ли я использовать OpenSSL или gnutls с Apache 2.4?Соединение с mysql (MariaDB) завершается ошибкой в ​​python-> SQLAlchemy, в то время как оно успешно выполняется в cmdNetlify Сетевой сокет клиента SSR Apollo NextJS отключен до того, как было установлено безопасное соединение TLSРазные хосты (поды в Kubernetes) отвечают разными сертификатами для одного и того же имени хоста.Почему этот Java HttpsServer не может успешно выполнить рукопожатие TLS?