Может ли подтверждение сертификата быть завершено, если SSL Labs показывает, что цепочка сертификатов этого сервера неполная. Оценка ограничена B.?

Я пытаюсь использовать urllib.request.urlopen на веб-сайте, начинающемся с «https». Вывод ошибки: ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed

Есть много отличных тем, которые освещают эту ошибку. В том числе этого, в котором упоминается рейтинг SSL Labs. Я могу использовать urllib.request.urlopen на любом другом https-сайте, который я тестировал.

SSL Labs показывает следующий вывод:

Key                        RSA 2048 bits (e 65537)
Issuer                     Let's Encrypt Authority X3
AIA:                       http://cert.int-x3.letsencrypt.org/
Signature algorithm        SHA256withRSA
Extended Validation        No
Certificate Transparency   No
OCSP Must Staple           No
Revocation information     OCSP 
Revocation status          Good (not revoked)
DNS CAA                    No (more info)
Trusted                    Yes

Чтобы уточнить, мой вопрос: есть ли решение для завершения рукопожатия, которое не включает обход проверки сертификата? И если есть решение, можно ли его решить полностью внутри скрипта Python в Linux, macOS и Windows?


person Bob    schedule 27.07.2017    source источник
comment
Укажите имя сервера или URL-адрес, который вы используете. Почему вы опускаете важную информацию? Вероятно, это ваш дубликат: Добавить файл CA SSL с помощью urllib2. Вот как это устранить: CERTIFICATE_VERIFY_FAILED при использовании urllib для подключения к almerys.com.   -  person jww    schedule 27.07.2017
comment
Если в браузере (клиентской библиотеке и т. д.) уже есть недостающая часть, он может завершить рукопожатие, но это означает, что вы уменьшаете количество клиентов, которые могут общаться с вашим сервером. Во всяком случае, вы спрашиваете об обходе этого со стороны клиента (например, путем добавления отсутствующего промежуточного сертификата CA в хранилище клиента) или об исправлении сервера (как вам действительно следует, если он находится под контролем вашей/вашей организации)?   -  person Charles Duffy    schedule 27.07.2017
comment
@jww Я пропустил эту критическую информацию, потому что не думал, что это важно. Я подумал, что это общая проблема, которая может возникнуть у других. Однако я не уверен, насколько мои рассуждения и мыслительный процесс имеют отношение к решению проблемы. Первое предложение в вашем комментарии стоит само по себе без второго предложения. Но спасибо за вторую ссылку, которую вы предоставили. Я смог загрузить файл .pem из Let's Encrypt, и это решило проблему. Я подробно описал шаги в этом решении.   -  person Bob    schedule 28.07.2017
comment
(Кстати, я бы сказал, что первичная важность URL-адреса заключается в том, чтобы позволить людям фактически тестировать решения для работы в вашем реальном сценарии использования; без тестового примера попытка решения является догадкой - ни человек, пишущий его, ни другой пользователь сайта, пытающийся определить, голосовать ли за что-то за или против, может со 100% уверенностью определить, действительно ли это правильно для реального сценария).   -  person Charles Duffy    schedule 28.07.2017
comment
(... интерпретация проблемы с отсутствующим промежуточным сертификатом, безусловно, является наиболее очевидной вещью, которая соответствует предоставленной информации, но я не собираюсь утверждать, что это единственная возможная причина рассматриваемой ошибки).   -  person Charles Duffy    schedule 28.07.2017


Ответы (3)


Это можно обойти, добавив отсутствующий промежуточный сертификат в активный X509Store< /а>:

cert_text = '''
-----BEGIN CERTIFICATE-----
...put your actual certificate text here...
-----END CERTIFICATE-----
'''

# fill this out depending on which specific intermediate cert you're missing
missing_cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_text)

context = ssl.create_default_context() # load default trusted certificates
store = context.get_cert_store()       # get the X509Store for that context
store.add_cert(missing_cert)           # add your missing cert to it

urllib.request.urlopen(site, context=context)

Обратите внимание: если вам только нужно обратиться к одному серверу, для которого вы это делаете, вы можете просто передать соответствующий аргумент cafile или capath в create_default_context().

person Charles Duffy    schedule 27.07.2017
comment
@ Чарльз Даффи Я только что прочитал ваш ответ на квазирешение, которое я опубликовал. Я совсем новичок в этом, поэтому я не совсем понимаю, что вы написали. Я очень заинтересован в вашем решении. Что должно заменить ... в missing_cert = OpenSSL.crypto.load_publickey(...)? - person Bob; 28.07.2017
comment
Это зависит от того, как закодирована копия ключа, который вы загрузили и встроили в исходный код. Если это PEM (что обычно), вашим первым аргументом будет OpenSSL.crypto.FILETYPE_PEM; независимо от этого, вторым аргументом будет буфер, в котором у вас есть сертификат (в этом случае закодированный в PEM). - person Charles Duffy; 28.07.2017
comment
@ Чарльз Даффи Кажется, я понимаю. Я бы загрузил .pem с сайта https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem. Затем включите этот файл в корневой каталог репозитория github. Затем в скрипт Python я бы включил missing_cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, PATH_TO_PEM_FILE_HERE), а затем дополнительные 4 строки, которые вы написали? - person Bob; 28.07.2017
comment
Не путь к файлу PEM, а текст файла PEM — вы можете поместить содержимое файла PEM прямо в исходный код Python в виде строки, как в приведенном выше примере (оба BEGIN CERTIFICATE и END CERTIFICATE — это то, что вы можете найти в сам PEM-файл). - person Charles Duffy; 28.07.2017
comment
@ Чарльз Даффи Я добавил строку import ssl, OpenSSL. Но теперь я получаю сообщение об ошибке AttributeError: 'SSLContext' object has no attribute 'get_cert_store' в строке store = context.get_cert_store(). Я гуглил ошибку, но ничего не появляется. Любые идеи? - person Bob; 28.07.2017
comment
Я нашел что-то, что работает, но я не уверен, что это будет иметь какие-либо непредвиденные последствия. Я загрузил файл .pem в свой рабочий каталог. Единственные 3 строки: context = ssl.create_default_context(), за которыми следуют context.load_verify_locations(cafile='/path/to/filename.pem') и page = urllib.request.urlopen(site_url_here, context=context).read(). - person Bob; 28.07.2017
comment
Это нормально, если вам не нужно подключаться к каким-либо серверам, не использующим рассматриваемый промежуточный сертификат. - person Charles Duffy; 28.07.2017

Я не могу ответить на этот вопрос для urllib, но вместо этого я смог решить эту проблему, используя запросы python. Обратите внимание, что это будет работать только в том случае, если для рассматриваемого веб-сайта существует доверенная цепочка сертификатов, но на сервере, возможно, отсутствует корневой или промежуточный сертификат.

Используя тест сервера SSL labs (ссылка здесь), запустите тест и прокрутите вниз до путей сертификации. ЕСЛИ дело в том, что есть доверенные пути сертификатов, но сервер по какой-то причине не предоставляет полную цепочку, вы можете скачать полный доверенный путь здесь в виде текста. Скопируйте полную цепочку сертификатов и сохраните ее как файл .pem и передайте путь к этому файлу в функцию запросов:

r = requests.get(url, verify = "path/to/chain.pem")

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

person Digestible1010101    schedule 02.01.2021

Мне удалось решить эту проблему (для системы на основе Debian я использую Debian 9). Мне все еще нужно протестировать решения на macOS и Windows.

В отчете SSL Labs под заголовком "Certification Paths" было показано:

1   Sent by server  www.exampleSITE.com

BLAH BLAH BLAH

2   Extra download  Let's Encrypt Authority X3

BLAH BLAH BLAH

3   In trust store  DST Root CA X3   Self-signed

BLAH BLAH BLAH

Я перешел к /etc/ssl/certs/ и заметил, что сертификатов Let's Encrypt нет. Затем я скачал .pem и перефразировал.

cd /etc/ssl/certs
sudo wget https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem
sudo c_rehash

Затем я протестировал строку python, которая раньше выдавала мне ошибку.

page = urllib.request.urlopen('https://www.exampleSITE.com').read()

и он успешно извлек страницу.

person Bob    schedule 27.07.2017
comment
@Charles Duffy Чтобы уточнить: я пишу скрипт на Python, который открывается и считывается с веб-сайта (не моего) и анализирует некоторую информацию, которая будет использоваться позже в сценарии. Я мог получить доступ к этому веб-сайту из Firefox, но не при использовании urllib в моем скрипте Python. Я могу попросить пользователей, запускающих мой скрипт, загрузить файл Let's Encrypt .pem перед его запуском. - person Bob; 28.07.2017
comment
Попался. Если бы вы использовали мой ответ, вам не нужно было бы просить своих пользователей что-либо изменить в /etc, поскольку вы бы встраивали отсутствующий сертификат непосредственно в скрипт, который в нем нуждается. - person Charles Duffy; 28.07.2017