Мне нужно написать сценарий, который подключается к множеству сайтов в нашей корпоративной интрасети через HTTPS и проверяет действительность их сертификатов SSL; что срок их действия не истек, что они выпущены для правильного адреса и т. д. Мы используем наш собственный внутренний корпоративный центр сертификации для этих сайтов, поэтому у нас есть открытый ключ ЦС для проверки сертификатов.
Python по умолчанию просто принимает и использует сертификаты SSL при использовании HTTPS, поэтому даже если сертификат недействителен, библиотеки Python, такие как urllib2 и Twisted, будут с радостью использовать этот сертификат.
Есть ли где-нибудь хорошая библиотека, которая позволит мне подключиться к сайту по HTTPS и таким образом проверить его сертификат?
Как проверить сертификат в Python?
источник
treq
илиtwisted.web.client.Agent
старше, Twisted проверяет сертификаты по умолчанию.Ответы:
Начиная с версии 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
https://docs.python.org/3/library/http.client.html#http.client.HTTPSConnection
Обратите внимание, что новая встроенная проверка основана на базе данных сертификатов, предоставленной системой . В отличие от этого, пакет запросов отправляет собственный пакет сертификатов. Плюсы и минусы обоих подходов обсуждаются в разделе базы данных доверия PEP 476 .
источник
HTTPSConnection
класс? Я использовалSSLSocket
. Как я могу выполнить проверкуSSLSocket
? Должен ли я явно подтверждать использование,pyopenssl
как описано здесь ?Я добавил дистрибутив в индекс пакета Python, который делает
match_hostname()
функцию изssl
пакета Python 3.2 доступной в предыдущих версиях Python.http://pypi.python.org/pypi/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: ...
источник
getpeercert()
метод, вызываемый для передачи выводаmatch_hostname()
.Вы можете использовать Twisted для проверки сертификатов. Главный API - это CertificateOptions , который может быть предоставлен в качестве
contextFactory
аргумента для различных функций, таких как listenSSL и startTLS .К сожалению, ни Python, ни Twisted не содержат ни кучи сертификатов CA, необходимых для фактической проверки HTTPS, ни логики проверки HTTPS. Из- за ограничений в PyOpenSSL вы пока не можете сделать это полностью правильно, но благодаря тому факту, что почти все сертификаты включают субъект commonName, вы можете подойти достаточно близко.
Вот наивный образец реализации проверяющего клиента Twisted HTTPS, который игнорирует подстановочные знаки и расширения subjectAltName и использует сертификаты центра сертификации, присутствующие в пакете ca-Certificates в большинстве дистрибутивов Ubuntu. Попробуйте это со своими любимыми сайтами с действующими и недействительными сертификатами :).
import os import glob from OpenSSL.SSL import Context, TLSv1_METHOD, VERIFY_PEER, VERIFY_FAIL_IF_NO_PEER_CERT, OP_NO_SSLv2 from OpenSSL.crypto import load_certificate, FILETYPE_PEM from twisted.python.urlpath import URLPath from twisted.internet.ssl import ContextFactory from twisted.internet import reactor from twisted.web.client import getPage certificateAuthorityMap = {} for certFileName in glob.glob("/etc/ssl/certs/*.pem"): # There might be some dead symlinks in there, so let's make sure it's real. if os.path.exists(certFileName): data = open(certFileName).read() x509 = load_certificate(FILETYPE_PEM, data) digest = x509.digest('sha1') # Now, de-duplicate in case the same cert has multiple names. certificateAuthorityMap[digest] = x509 class HTTPSVerifyingContextFactory(ContextFactory): def __init__(self, hostname): self.hostname = hostname isClient = True def getContext(self): ctx = Context(TLSv1_METHOD) store = ctx.get_cert_store() for value in certificateAuthorityMap.values(): store.add_cert(value) ctx.set_verify(VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT, self.verifyHostname) ctx.set_options(OP_NO_SSLv2) return ctx def verifyHostname(self, connection, x509, errno, depth, preverifyOK): if preverifyOK: if self.hostname != x509.get_subject().commonName: return False return preverifyOK def secureGet(url): return getPage(url, HTTPSVerifyingContextFactory(URLPath.fromString(url).netloc)) def done(result): print 'Done!', len(result) secureGet("https://google.com/").addCallback(done) reactor.run()
источник
URLPath(url).netloc
: это означает, что часть URL-адреса, переданного в secureGet, является хостом. Другими словами, он проверяет, совпадает ли commonName субъекта с тем, которое запрашивает вызывающий.https://www.google.com/
потому что одна из тем - www.google.com, в результате чего функция возвращает False. Вероятно, это означало вернуть True (принято), если имена совпадают, и False, если нет?PycURL прекрасно это делает.
Ниже приведен небольшой пример.
pycurl.error
Если что-то не так, он выдаст a , где вы получите кортеж с кодом ошибки и удобочитаемым сообщением.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 requests requests.get('https://somesite.com', cert='/path/server.crt', verify=True)
Еще несколько слов о его использовании.
источник
cert
Аргумент сертификат на стороне клиента, а не сертификат сервера , чтобы проверить против. Вы хотите использоватьverify
аргумент.verify
аргумент, за исключением того, что он является более явным или отключает проверку.Вот пример сценария, демонстрирующего проверку сертификата:
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()
источник
CertValidatingHTTPSConnection.connect
. См. Этот запрос на перенос для получения подробной информации (и исправления).backports.ssl_match_hostname
.M2Crypto может выполнить проверку . Вы также можете использовать M2Crypto с Twisted, если хотите. Настольный клиент Chandler использует Twisted для работы в сети и M2Crypto для SSL , включая проверку сертификатов.
На основе комментария Glyphs кажется, что 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/
источник
Следующий код позволяет использовать все проверки SSL (например, действительность даты, цепочку сертификатов CA ...), ЗА ИСКЛЮЧЕНИЕМ подключаемого этапа проверки, например, для проверки имени хоста или выполнения других дополнительных шагов проверки сертификата.
from httplib import HTTPSConnection import ssl def create_custom_HTTPSConnection(host): def verify_cert(cert, host): # Write your code here # You can certainly base yourself on ssl.match_hostname # Raise ssl.CertificateError if verification fails print 'Host:', host print 'Peer cert:', cert class CustomHTTPSConnection(HTTPSConnection, object): def connect(self): super(CustomHTTPSConnection, self).connect() cert = self.sock.getpeercert() verify_cert(cert, host) context = ssl.create_default_context() context.check_hostname = False return CustomHTTPSConnection(host=host, context=context) if __name__ == '__main__': # try expired.badssl.com or self-signed.badssl.com ! conn = create_custom_HTTPSConnection('badssl.com') conn.request('GET', '/') conn.getresponse().read()
источник
pyOpenSSL - это интерфейс к библиотеке OpenSSL. Он должен предоставить все необходимое.
источник
У меня была та же проблема, но я хотел свести к минимуму сторонние зависимости (потому что этот одноразовый скрипт должен был выполняться многими пользователями). Мое решение заключалось в том, чтобы обернуть
curl
вызов и убедиться, что код выхода был0
. Работал как шарм.источник