wake-up-neo.com

Validieren Sie SSL-Zertifikate mit Python

Ich muss ein Skript schreiben, das über HTTPS eine Verbindung zu einer Reihe von Websites in unserem Unternehmensintranet herstellt und überprüft, ob deren SSL-Zertifikate gültig sind. dass sie nicht abgelaufen sind, dass sie für die richtige Adresse ausgestellt wurden usw. Wir verwenden für diese Sites unsere eigene interne Zertifizierungsstelle, sodass wir über den öffentlichen Schlüssel der Zertifizierungsstelle verfügen, anhand dessen die Zertifikate überprüft werden.

Python akzeptiert und verwendet standardmäßig nur SSL-Zertifikate, wenn HTTPS verwendet wird. Selbst wenn ein Zertifikat ungültig ist, verwenden Python Bibliotheken wie urllib2 und Twisted das Zertifikat nur mit Freude.

Gibt es eine gute Bibliothek, mit der ich über HTTPS eine Verbindung zu einer Site herstellen und deren Zertifikat auf diese Weise überprüfen kann?

Wie überprüfe ich ein Zertifikat in Python?

81
Eli Courtwright

Ab Release-Version 2.7.9/3.4.3 versucht Python standardmäßig , die Zertifikatüberprüfung durchzuführen.

Dies wurde in PEP 467 vorgeschlagen, das eine Lektüre wert ist: https://www.python.org/dev/peps/pep-0476/

Die Änderungen betreffen alle relevanten stdlib-Module (urllib/urllib2, http, httplib).

Relevante Unterlagen:

https://docs.python.org/2/library/httplib.html#httplib.HTTPSConnection

Diese Klasse führt nun standardmäßig alle erforderlichen Zertifikats- und Hostnamenprüfungen durch. Um zum vorherigen, nicht verifizierten Verhalten zurückzukehren, kann ssl._create_unverified_context () an den context-Parameter übergeben werden.

https://docs.python.org/3/library/http.client.html#http.client.HTTPSConnection

In Version 3.4.3 geändert: Diese Klasse führt nun standardmäßig alle erforderlichen Zertifikats- und Hostnamenprüfungen durch. Um zum vorherigen, nicht verifizierten Verhalten zurückzukehren, kann ssl._create_unverified_context () an den context-Parameter übergeben werden.

Beachten Sie, dass die neue integrierte Überprüfung auf der Zertifikatsdatenbank vom System bereitgestellt basiert. Im Gegensatz dazu liefert das Paket Anfragen ein eigenes Zertifikatspaket. Vor- und Nachteile beider Ansätze werden im Abschnitt Vertrauensdatenbank von PEP 476 erläutert.

18

Ich habe dem Paketindex Python=) eine Distribution hinzugefügt, die die Funktion match_hostname() aus dem Paket Python 3.2 ssl erstellt verfügbar in früheren Versionen von Python.

http://pypi.python.org/pypi/backports.ssl_match_hostname/

Sie können es installieren mit:

pip install backports.ssl_match_hostname

Sie können es auch zu einer Abhängigkeit machen, die im setup.py Ihres Projekts aufgeführt ist. In beiden Fällen kann es folgendermaßen verwendet werden:

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:
    ...
30
Brandon Rhodes

Mit Twisted können Sie Zertifikate überprüfen. Die Haupt-API ist CertificateOptions , die als Argument contextFactory für verschiedene Funktionen wie listenSSL und startTLS bereitgestellt werden kann.

Leider enthält weder Python noch Twisted einen Stapel von CA-Zertifikaten, die für die eigentliche HTTPS-Validierung erforderlich sind, noch die HTTPS-Validierungslogik. Aufgrund von eine Einschränkung in PyOpenSSL , Sie können es noch nicht ganz richtig machen, aber dank der Tatsache, dass fast alle Zertifikate einen Betreff commonName enthalten, können Sie nah genug heran kommen.

Hier ist eine naive Beispielimplementierung eines verifizierenden Twisted HTTPS-Clients, der Platzhalter und subjectAltName-Erweiterungen ignoriert und die Zertifikate der Zertifizierungsstelle verwendet, die in den meisten Ubuntu-Distributionen im Paket 'ca-certificates' enthalten sind. Probieren Sie es mit Ihren bevorzugten gültigen und ungültigen Zertifikatsseiten aus :).

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

PycURL macht das wunderbar.

Unten ist ein kurzes Beispiel. Es wird ein pycurl.error Ausgelöst, wenn etwas faul ist, und Sie erhalten ein Tupel mit Fehlercode und eine vom Menschen lesbare Nachricht.

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

Möglicherweise möchten Sie weitere Optionen konfigurieren, z. B. den Speicherort für die Ergebnisse usw. Sie müssen das Beispiel jedoch nicht mit Unwesentlichem überladen.

Beispiel für mögliche Ausnahmen:

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

Einige Links, die ich nützlich fand, sind die libcurl-docs für setopt und getinfo.

25
plundra

Oder vereinfachen Sie einfach Ihr Leben mit der Anfragen Bibliothek:

import requests
requests.get('https://somesite.com', cert='/path/server.crt', verify=True)

Noch ein paar Worte zur Verwendung.

14
ufo

Hier ist ein Beispielskript, das die Zertifikatvalidierung demonstriert:

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 __== "__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()
14
Eli Courtwright

M2Crypto can Validierung durchführen . Sie können auch M2Crypto with Twisted verwenden, wenn Sie möchten. Der Chandler-Desktop-Client verwendet Twisted für das Netzwerk und M2Crypto für SSL , einschließlich Zertifikatüberprüfung.

Basierend auf Glyphen-Kommentaren scheint M2Crypto standardmäßig eine bessere Zertifikatsüberprüfung durchzuführen, als dies derzeit mit pyOpenSSL möglich ist, da M2Crypto auch das Feld subjectAltName überprüft.

Ich habe auch gebloggt, wie man die Zertifikate erhält Mozilla Firefox wird mit in Python und verwendbar mit Python SSL-Lösungen) geliefert.

8
Heikki Toivonen

Jython DOES führt standardmäßig eine Zertifikatüberprüfung durch, sodass Standardbibliotheksmodule verwendet werden, z. httplib.HTTPSConnection usw. mit Jython überprüft Zertifikate und gibt Ausnahmen für Fehler, d. h. nicht übereinstimmende Identitäten, abgelaufene Zertifikate usw.

Tatsächlich müssen Sie einige zusätzliche Arbeiten ausführen, damit sich Jython wie Cpython verhält, d. H. Damit Jython KEINE Zertifikate überprüft.

Ich habe einen Blog-Beitrag darüber geschrieben, wie die Zertifikatsprüfung in Jython deaktiviert werden kann, da dies in Testphasen usw. nützlich sein kann.

Installation eines vertrauenswürdigen Sicherheitsanbieters unter Java und jython.
http://jython.xhaus.com/installing-an-all-trusting-security-provider-on-Java-and-jython/

4
Alan Kennedy

Mit dem folgenden Code können Sie alle SSL-Validierungsprüfungen (z. B. Gültigkeitsdatum, CA-Zertifikatkette ...) AUSSER einem steckbaren Überprüfungsschritt, z. um den Hostnamen zu überprüfen oder andere zusätzliche Schritte zur Zertifikatüberprüfung auszuführen.

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 __== '__main__':
    # try expired.badssl.com or self-signed.badssl.com !
    conn = create_custom_HTTPSConnection('badssl.com')
    conn.request('GET', '/')
    conn.getresponse().read()
1
Carl D'Halluin