Сообщение об ошибке
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate
Также выводится как
- [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate
Python добавляет место в исходном коде на C, например «(_ssl.c:1082)». Не путайте эту ошибку с «self-signed certificate in certificate chain»: та означает цепочку CA, которая заканчивается недоверенным корневым сертификатом. OpenSSL 1.1.1 пишет «self signed».
Python ssl module (OpenSSL) — ssl.SSLCertVerificationError, X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT (18)
Что это означает
Сервер предъявил единственный самоподписанный сертификат (его издатель — он сам), которого нет в наборе доверенных CA этого контекста. Искать нечего: за сертификатом нет CA, поэтому OpenSSL выдаёт ошибку на глубине 0. Это типично для серверов разработки, специализированных устройств и свежих установок; решение — осознанно доверять этому сертификату или заменить его.
Почему это происходит
Сервер разработки или тестовый сервер со сгенерированным сертификатом
частоЛокальные серверы, запущенные с временным сертификатом (ssl_context="adhoc" во Flask, uvicorn со сгенерированным ключом, стеки docker-compose), предъявляют только что созданный самоподписанный сертификат, которому никто не доверяет.
Устройство или продукт с заводским сертификатом
частоNAS-устройства, маршрутизаторы, Proxmox, Elasticsearch, Jenkins и многие специализированные устройства от производителей работают с самоподписанным сертификатом, пока администратор не установит нормальный.
Прокси отдаёт встроенный сертификат по умолчанию
иногдаIngress-контроллеры и обратные прокси отвечают на запросы к неизвестным именам хостов самоподписанным сертификатом по умолчанию; настоящая проблема тогда в маршрутизации или SNI.
Как исправить
- 1.
Доверяйте только этому сертификату и только в этом клиенте
Загрузите собственный сертификат сервера как cafile. Проверки имени хоста и срока действия остаются включёнными, а принимается только этот конкретный сертификат.
python import ssl, urllib.request # Trust exactly this certificate (fetched and fingerprint-checked out of band) ctx = ssl.create_default_context(cafile="api.example.com.pem") urllib.request.urlopen("https://api.example.com:8443/", context=ctx) - 2.
Получите сертификат и сверьте его отпечаток
Прежде чем доверять файлу, сверьте отпечаток SHA-256 со значением, которое показывает консоль устройства или сообщает его администратор.
bash openssl s_client -connect api.example.com:8443 -servername api.example.com </dev/null 2>/dev/null \ | openssl x509 -out api.example.com.pem openssl x509 -in api.example.com.pem -noout -fingerprint -sha256 # compare with the server's console - 3.
При разработке: используйте вместо этого локальный CA
Локальный CA позволяет доверять одному корневому сертификату и выпускать правильно подписанные сертификаты для всех хостов разработки, поэтому ни одному клиенту не нужны исключения для отдельных сертификатов.
bash # Development: one local CA, many certificates (mkcert installs the CA for you) mkcert -install mkcert api.example.test export SSL_CERT_FILE="$(mkcert -CAROOT)/rootCA.pem" - 4.
Временная диагностика: отключите проверку в тесте
check_hostname = False и CERT_NONE отключают все проверки и принимают любой сертификат, в том числе предъявленный злоумышленником. Используйте эту настройку только для того, чтобы убедиться, что проблема исключительно в доверии, а затем уберите её.
python # Temporary diagnostic only: proves trust is the sole problem. Never ship this. import ssl ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE