Вопрос

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

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

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

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

Это было полезно?

Решение

Начиная с версии 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.

Обратите внимание, что новая встроенная проверка основана на предоставляемый системой база данных сертификатов.В противовес этому, Запросы посылка поставляется с собственным пакетом сертификатов.Плюсы и минусы обоих подходов обсуждаются в Доверенная база данных раздел PEP 476.

Другие советы

Я добавил дистрибутив в индекс пакета 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 является Варианты сертификации, который может быть предоставлен в качестве contextFactory аргумент для различных функций, таких как слушаеТSL и стартТЛЫ.

К сожалению, ни Python, ни Twisted не поставляются с кучей сертификатов CA, необходимых для фактической проверки HTTPS, ни с логикой проверки HTTPS.Из-за ограничение в pyOpenSSL, вы пока не можете сделать это полностью корректно, но благодаря тому факту, что почти все сертификаты содержат общее имя субъекта, вы можете подобраться достаточно близко.

Вот наивный пример реализации проверяющего скрученного 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()

ПикУРЛ делает это прекрасно.

Ниже приведен краткий пример.Это вызовет 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 использует Twisted для работы в сети и M2Crypto для SSL, включая проверку подлинности сертификата.

Основываясь на комментарии Glyphs, кажется, что M2Crypto по умолчанию выполняет лучшую проверку сертификата, чем то, что вы можете сделать с pyOpenSSL в настоящее время, потому что M2Crypto также проверяет поле subjectAltName .

Я также писал в блоге о том, как получите сертификаты Mozilla Firefox поставляется с SSL-решениями на Python и может использоваться с Python.

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()

пиОпенССЛ является интерфейсом к библиотеке OpenSSL.Он должен предоставить все, что вам нужно.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top