Проверка сертификатов SSL с помощью Python
Мне нужно написать сценарий, который подключается к куче сайтов в нашей корпоративной интрасети по HTTPS и проверяет, что их SSL-сертификаты действительны; что они не истекли, что они выданы для правильного адреса и т. д. Мы используем наш собственный внутренний корпоративный центр сертификации для этих сайтов, поэтому у нас есть открытый ключ ЦС для проверки сертификатов.
Python по умолчанию просто принимает и использует SSL-сертификаты при использовании HTTPS, поэтому даже если сертификат недействителен, библиотеки Python, такие как urllib2 и Twisted, с радостью будут использовать сертификат.
есть ли хорошая библиотека, которая позволит мне подключиться к сайту через HTTPS и проверить его сертификат таким образом?
Как проверить сертификат в Python?
10 ответов
из версии версии 2.7.9 / 3.4.3 on, Python по умолчанию пытается выполнить проверку сертификата.
Это было предложено в PEP 467, который стоит прочитать:https://www.python.org/dev/peps/pep-0476/
изменения затрагивают все соответствующие модули stdlib (urllib/urllib2, http, httplib).
соответствующие документация:
https://docs.python.org/2/library/httplib.html#httplib.HTTPSConnection
этот класс теперь выполняет все необходимые проверки сертификата и имени хоста по умолчанию. Чтобы вернуться к предыдущему, непроверенному поведению ssl._create_unverified_context () можно передать в параметр context.
https://docs.python.org/3/library/http.client.html#http.client.HTTPSConnection
изменено в версии 3.4.3: этот класс теперь выполняет все необходимые проверки сертификата и имени хоста по умолчанию. Чтобы вернуться к предыдущему, непроверенному поведению ssl._create_unverified_context () можно передать в параметр context.
обратите внимание, что новая встроенная проверка основана на
я добавил дистрибутив в индекс пакета Python, который делает match_hostname()
функция из Python 3.2 ssl
пакет доступен в предыдущих версиях Python.
http://pypi.python.org/pypi/backports.ssl_match_hostname/
вы можете установить его с:
pip install backports.ssl_match_hostname
или вы можете сделать его зависимостью, указанной вsetup.py
. В любом случае, его можно использовать следующим образом:
from backports.ssl_match_hostname import match_hostname, CertificateError
...
sslsock = ssl.wrap_socket(sock, ssl_version=ssl.PROTOCOL_SSLv3,
cert_reqs=ssl.CERT_REQUIRED, ca_certs=...)
try:
match_hostname(sslsock.getpeercert(), hostname)
except CertificateError, ce:
...
Вы можете использовать Twisted для проверки сертификатов. Основным API является CertificateOptions, который может быть предоставлен как
pycurl из это красиво.
Ниже приведен краткий пример. Он бросит pycurl.error
если что-то подозрительно, где вы получаете кортеж с кодом ошибки и читаемым человеком сообщением.
import pycurl
curl = pycurl.Curl()
curl.setopt(pycurl.CAINFO, "myFineCA.crt")
curl.setopt(pycurl.SSL_VERIFYPEER, 1)
curl.setopt(pycurl.SSL_VERIFYHOST, 2)
curl.setopt(pycurl.URL, "https://internal.stuff/")
curl.perform()
вы, вероятно, захотите настроить больше параметров, например, где хранить результаты и т. д. Но не нужно загромождать пример несущественным.
пример того, какие исключения могут быть подняты:
(60, 'Peer certificate cannot be authenticated with known CA certificates')
(51, "common name 'CN=something.else.stuff,O=Example Corp,C=SE' does not match 'internal.stuff'")
некоторые ссылки, которые я нашел полезны libcurl-документы для setopt и getinfo.
вот пример сценария, который демонстрирует проверку сертификата:
import httplib
import re
import socket
import sys
import urllib2
import ssl
class InvalidCertificateException(httplib.HTTPException, urllib2.URLError):
def __init__(self, host, cert, reason):
httplib.HTTPException.__init__(self)
self.host = host
self.cert = cert
self.reason = reason
def __str__(self):
return ('Host %s returned an invalid certificate (%s) %s\n' %
(self.host, self.reason, self.cert))
class CertValidatingHTTPSConnection(httplib.HTTPConnection):
default_port = httplib.HTTPS_PORT
def __init__(self, host, port=None, key_file=None, cert_file=None,
ca_certs=None, strict=None, **kwargs):
httplib.HTTPConnection.__init__(self, host, port, strict, **kwargs)
self.key_file = key_file
self.cert_file = cert_file
self.ca_certs = ca_certs
if self.ca_certs:
self.cert_reqs = ssl.CERT_REQUIRED
else:
self.cert_reqs = ssl.CERT_NONE
def _GetValidHostsForCert(self, cert):
if 'subjectAltName' in cert:
return [x[1] for x in cert['subjectAltName']
if x[0].lower() == 'dns']
else:
return [x[0][1] for x in cert['subject']
if x[0][0].lower() == 'commonname']
def _ValidateCertificateHostname(self, cert, hostname):
hosts = self._GetValidHostsForCert(cert)
for host in hosts:
host_re = host.replace('.', '\.').replace('*', '[^.]*')
if re.search('^%s$' % (host_re,), hostname, re.I):
return True
return False
def connect(self):
sock = socket.create_connection((self.host, self.port))
self.sock = ssl.wrap_socket(sock, keyfile=self.key_file,
certfile=self.cert_file,
cert_reqs=self.cert_reqs,
ca_certs=self.ca_certs)
if self.cert_reqs & ssl.CERT_REQUIRED:
cert = self.sock.getpeercert()
hostname = self.host.split(':', 0)[0]
if not self._ValidateCertificateHostname(cert, hostname):
raise InvalidCertificateException(hostname, cert,
'hostname mismatch')
class VerifiedHTTPSHandler(urllib2.HTTPSHandler):
def __init__(self, **kwargs):
urllib2.AbstractHTTPHandler.__init__(self)
self._connection_args = kwargs
def https_open(self, req):
def http_class_wrapper(host, **kwargs):
full_kwargs = dict(self._connection_args)
full_kwargs.update(kwargs)
return CertValidatingHTTPSConnection(host, **full_kwargs)
try:
return self.do_open(http_class_wrapper, req)
except urllib2.URLError, e:
if type(e.reason) == ssl.SSLError and e.reason.args[0] == 1:
raise InvalidCertificateException(req.host, '',
e.reason.args[1])
raise
https_request = urllib2.HTTPSHandler.do_request_
if __name__ == "__main__":
if len(sys.argv) != 3:
print "usage: python %s CA_CERT URL" % sys.argv[0]
exit(2)
handler = VerifiedHTTPSHandler(ca_certs = sys.argv[1])
opener = urllib2.build_opener(handler)
print opener.open(sys.argv[2]).read()
или просто сделать вашу жизнь проще с помощью запросы библиотека:
import requests
requests.get('https://somesite.com', cert='/path/server.crt', verify=True)
M2Crypto can выполнить проверку. Вы также можете использовать M2Crypto с Витой Если вам нравится. Настольный клиент Chandler использует Twisted для сетей и M2Crypto для SSL, включая проверку сертификата.
на основе комментария глифов кажется, что M2Crypto делает лучшую проверку сертификата по умолчанию, чем то, что вы можете сделать с pyOpenSSL в настоящее время, потому что m2crypto проверяет поле subjectAltName тоже.
Я также в блоге о том, как получить сертификаты Mozilla Firefox поставляется с Python и может использоваться с решениями Python SSL.
Jython выполняет проверку сертификата по умолчанию, поэтому использует стандартные библиотечные модули, например httplib.HTTPSConnection и т. д. С jython проверит сертификаты и даст исключения для сбоев, т. е. несоответствующие идентификаторы, истекшие сертификаты и т. д.
на самом деле, вы должны сделать дополнительную работу, чтобы заставить jython вести себя как cpython, т. е. заставить jython не проверять сертификаты.
Я написал сообщение в блоге о том, как отключить проверку сертификатов на jython, потому что это могут быть полезны на этапах тестирования и т. д.
установка полностью доверяющего поставщика безопасности на java и jython.
http://jython.xhaus.com/installing-an-all-trusting-security-provider-on-java-and-jython/
pyOpenSSL - это интерфейс к библиотеке OpenSSL. Он должен обеспечить все, что вам нужно.
у меня была такая же проблема, но я хотел свести к минимуму зависимости 3rd (потому что этот одноразовый скрипт должен был выполняться многими пользователями). Моим решением было обернуть curl
позвоните и убедитесь, что код выхода был 0
. Сработало как по волшебству.