Проверка SSL-сертификатов с помощью Python
Мне нужно написать скрипт, который подключается к куче сайтов в нашей корпоративной интрасети по HTTPS и проверяет, что их SSL-сертификаты действительны; что они не истекли, что они выданы для правильного адреса и т. д. Мы используем наш собственный внутренний корпоративный центр сертификации для этих сайтов, поэтому у нас есть открытый ключ центра сертификации для проверки сертификатов.
Python по умолчанию просто принимает и использует SSL-сертификаты при использовании HTTPS, поэтому даже если сертификат недействителен, библиотеки Python, такие как urllib2 и Twisted, будут просто счастливо использовать сертификат.
есть ли где-нибудь хорошая библиотека, которая позволит мне подключиться к сайту через HTTPS и проверить его сертификат таким образом?
Как проверить сертификат в Python?
10 ответов:
начиная с версии 2.7.9 / 3.4.3, 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.2ssl
пакет доступен в предыдущих версиях 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 для проверки сертификатов. Основными Иза 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-docs для 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 можете выполнить проверку. Вы также можете использовать M2Crypto с Витой Если вам нравится. Клиент рабочего стола Chandler использует витой для сетей и 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. Он должен обеспечить все, что вам нужно.