Question

Je dois écrire un script qui se connecte à un tas de sites sur notre intranet de l'entreprise via HTTPS et vérifie que leurs certificats SSL sont valides; qu'ils ne sont pas périmés, qu'ils sont émis pour la bonne adresse, etc. Nous utilisons notre propre autorité de certification d'entreprise interne pour ces sites, nous avons donc la clé publique de l'autorité de certification pour vérifier les certificats contre.

Python par défaut accepte juste et utilise des certificats SSL lorsque vous utilisez HTTPS, même si un certificat est valide, les bibliothèques Python telles que urllib2 et entortillé simplement utiliser joyeusement le certificat.

Y at-il une bonne bibliothèque quelque part qui me permettra de connecter à un site via HTTPS et vérifie son certificat de cette façon?

Comment puis-je vérifier un certificat en Python?

Était-ce utile?

La solution

A partir de la version de version 2.7.9 / 3.4.3, Python par défaut tente d'effectuer la validation du certificat.

Cela a été proposé dans le PEP 467, qui mérite une lecture: https: //www.python. org / dev / EPEP / pep-0476 /

Les modifications affectent tous les modules de stdlib pertinents (urllib / urllib2, http, httplib).

La documentation pertinente:

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

  

Cette classe effectue maintenant tous les certificats nécessaires et des vérifications hostname par défaut. Pour revenir à la précédente, non vérifiées, le comportement ssl._create_unverified_context () peut être passée au paramètre de contexte.

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

  

Changement dans la version 3.4.3: Cette classe effectue désormais tous les certificats nécessaires et des vérifications hostname par défaut. Pour revenir à la précédente, non vérifiées, le comportement ssl._create_unverified_context () peut être passée au paramètre de contexte.

Notez que la nouvelle vérification intégrée est basée sur le système fourni par base de certificats. Opposé à cela, le paquet demande navires son propre paquet de certificat. Avantages et inconvénients des deux approches sont discutées dans le base de données confiance de PEP 476 .

Autres conseils

J'ai ajouté une distribution au pypi qui rend la fonction match_hostname() du package ssl Python 3.2 disponible sur les versions précédentes de Python.

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

Vous pouvez l'installer avec:

pip install backports.ssl_match_hostname

Ou vous pouvez faire une dépendance dans la liste setup.py de votre projet. De toute façon, il peut être utilisé comme ceci:

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:
    ...

Vous pouvez utiliser Twisted pour vérifier les certificats. L'API principale est CertificateOptions , qui peut être à condition que l'argument contextFactory à diverses fonctions telles que listenSSL et startTLS .

Malheureusement, ni Python, ni tordu est livré avec un tas de certificats CA requis pour faire réellement la validation HTTPS, ni la logique de validation HTTPS. En raison de une limitation pyopenssl, vous ne pouvez pas le faire complètement correctement pour l'instant, mais grâce au fait que presque tous les certificats comprennent un sujet commonName, vous pouvez obtenir assez proche.

Voici une implémentation naïve exemple d'une vérification de client HTTPS Twisted qui ne tient pas compte des caractères génériques et les extensions de subjectAltName, et utilise les certificats certificats autorité dans le paquet « ca-certificates » dans la plupart des distributions Ubuntu. Essayez-le avec vos sites de certificats valides et non valides préférés:.)

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 Est-ce magnifiquement.

Voici un court exemple. Il lancera un pycurl.error si quelque chose est louche, où vous obtenez un tuple avec le code d'erreur et un message lisible par l'homme.

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

Vous voudrez probablement configurer plus d'options, comme l'endroit où stocker les résultats, etc. Mais pas besoin d'encombrer l'exemple avec les non-essentiels.

Exemple de ce que des exceptions peuvent être soulevées:

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

Quelques liens utiles que je trouve les libcurl-docs pour setopt et getinfo.

Voici un exemple de script qui démontre la validation du certificat:

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

M2Crypto pouvez faire la validation . Vous pouvez également utiliser M2Crypto avec Twisted si vous le souhaitez. Le client de bureau Chandler utilise Twisted pour la mise en réseau et M2Crypto pour SSL , y compris la validation du certificat.

Sur la base de Glyphes commentaire il semble que M2Crypto fait mieux vérification du certificat par défaut que ce que vous pouvez faire avec pyOpenSSL actuellement, parce que M2Crypto vérifications sur le terrain subjectAltName aussi.

J'ai aussi blogué sur la façon de obtenir les certificats navires de Mozilla Firefox avec en Python et Python utilisable avec des solutions SSL.

Jython ne procède à la vérification du certificat par défaut, en utilisant des modules de bibliothèque standard, par exemple httplib.HTTPSConnection, etc, avec Jython vérifiera les certificats et donner des exceptions pour les échecs, à savoir les identités ne correspondent pas, a expiré certs, etc.

En fait, vous devez faire un travail supplémentaire pour obtenir Jython à se comporter comme CPython, à savoir pour obtenir Jython de ne pas vérifier certs.

J'ai écrit un billet de blog sur la façon de désactiver la vérification des certificats sur Jython, car il peut être utile dans les phases de test, etc.

Installation d'un fournisseur de sécurité tout confiance sur Java et Jython.
http: //jython.xhaus. com / installation-un-tout-faire confiance à la sécurité fournisseur sur-java-et-Jython /

Le code suivant vous permet de bénéficier de tous les contrôles de validation SSL (par exemple la validité de la date, la chaîne de certificat CA ...) SAUF une étape de vérification par exemple connectable de vérifier le nom d'hôte ou faire d'autres étapes de vérification des certificats supplémentaires.

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 est une interface à la bibliothèque OpenSSL. Il devrait fournir tout ce dont vous avez besoin.

J'avais le même problème mais je voulais minimiser les dépendances 3ème partie (parce que ce script unique devait être exécuté par de nombreux utilisateurs). Ma solution a été d'envelopper un appel curl et assurez-vous que le code de sortie a été 0. A travaillé comme un charme.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top