Проверка SSL-сертификатов с помощью Python


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

Python по умолчанию просто принимает и использует SSL-сертификаты при использовании HTTPS, поэтому даже если сертификат недействителен, библиотеки Python, такие как urllib2 и Twisted, будут просто счастливо использовать сертификат.

есть ли где-нибудь хорошая библиотека, которая позволит мне подключиться к сайту через HTTPS и проверить его сертификат таким образом?

Как проверить сертификат в Python?

10 75

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.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 для проверки сертификатов. Основными Иза 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. Он должен обеспечить все, что вам нужно.

у меня была та же проблема, но я хотел свести к минимуму зависимости от третьих сторон (потому что этот одноразовый скрипт должен был выполняться многими пользователями). Мое решение было обернуть curl позвоните и убедитесь, что код выхода был 0. Сработало как по волшебству.