J'ai besoin d'écrire un script qui se connecte à un tas de sites sur notre intranet d'entreprise via HTTPS et vérifie que leurs certificats SSL sont valides; qu'ils ne sont pas expirés, qu'ils sont émis pour la bonne adresse, etc. Nous utilisons notre propre autorité de certification interne pour ces sites, nous avons donc la clé publique de l'autorité de certification pour vérifier les certificats.
Python par défaut accepte et utilise simplement les certificats SSL lors de l'utilisation de HTTPS, donc même si un certificat n'est pas valide, les bibliothèques Python telles que urllib2 et Twisted utiliseront simplement le certificat.
Y a-t-il une bonne bibliothèque quelque part qui me permettra de me connecter à un site via HTTPS et de vérifier son certificat de cette manière?
Comment vérifier un certificat en Python?
la source
treq
outwisted.web.client.Agent
depuis la version 14.0, Twisted vérifie les certificats par défaut.Réponses:
À partir de la version 2.7.9 / 3.4.3, Python tente par défaut d'effectuer la validation du certificat.
Cela a été proposé dans PEP 467, qui vaut la peine d'être lu: https://www.python.org/dev/peps/pep-0476/
Les modifications affectent tous les modules stdlib pertinents (urllib / urllib2, http, httplib).
Documentation pertinente:
https://docs.python.org/2/library/httplib.html#httplib.HTTPSConnection
https://docs.python.org/3/library/http.client.html#http.client.HTTPSConnection
Notez que la nouvelle vérification intégrée est basée sur la base de données de certificats fournie par le système. Contrairement à cela, le package de requêtes contient son propre ensemble de certificats. Les avantages et les inconvénients des deux approches sont examinés dans la section de la base de données Trust du PEP 476 .
la source
HTTPSConnection
classe? J'utilisaisSSLSocket
. Comment puis-je faire la validation avecSSLSocket
? Dois-je valider explicitement l'utilisationpyopenssl
comme expliqué ici ?J'ai ajouté une distribution à l'index des packages Python qui rend la
match_hostname()
fonction dussl
package 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:
Ou vous pouvez en faire une dépendance répertoriée dans votre projet
setup.py
. Dans tous les cas, 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: ...
la source
getpeercert()
méthode appelée pour que la sortie puisse être transmisematch_hostname()
.Vous pouvez utiliser Twisted pour vérifier les certificats. L'API principale est CertificateOptions , qui peut être fournie comme
contextFactory
argument de diverses fonctions telles que listenSSL et startTLS .Malheureusement, ni Python ni Twisted ne sont fournis avec la pile de certificats CA requis pour effectuer réellement la validation HTTPS, ni la logique de validation HTTPS. En raison d' une limitation dans PyOpenSSL , vous ne pouvez pas encore le faire complètement correctement, mais grâce au fait que presque tous les certificats incluent un sujet commonName, vous pouvez vous en approcher suffisamment.
Voici un exemple d'implémentation naïf d'un client HTTPS Twisted vérifiant qui ignore les caractères génériques et les extensions subjectAltName, et utilise les certificats d'autorité de certification présents dans le package 'ca-certificates' dans la plupart des distributions Ubuntu. Essayez-le avec vos sites de certificats valides et invalides 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()
la source
URLPath(url).netloc
: cela signifie la partie hôte de l'URL transmise à secureGet. En d'autres termes, il vérifie que le commonName du sujet est le même que celui demandé par l'appelant.https://www.google.com/
car l'un des sujets est «www.google.com», ce qui fait que la fonction renvoie False. Cela signifiait probablement retourner True (accepté) si les noms correspondent et False si ce n'est pas le cas?PycURL fait cela à merveille.
Voici un court exemple. Il lancera un
pycurl.error
si quelque chose est louche, où vous obtenez un tuple avec un 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'emplacement de stockage des résultats, etc. Mais pas besoin d'encombrer l'exemple avec des éléments non essentiels.
Exemple des exceptions qui pourraient ê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'")
Certains liens que j'ai trouvés utiles sont les libcurl-docs pour setopt et getinfo.
la source
Ou simplifiez-vous simplement la vie en utilisant la bibliothèque de requêtes :
import requests requests.get('https://somesite.com', cert='/path/server.crt', verify=True)
Quelques mots de plus sur son utilisation.
la source
cert
argument est le certificat côté client, pas un certificat de serveur à vérifier. Vous souhaitez utiliser l'verify
argument.verify
argument, sauf pour être plus explicite ou désactiver la vérification.Voici un exemple de script qui illustre 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()
la source
CertValidatingHTTPSConnection.connect
. Consultez cette pull request pour plus de détails (et un correctif).backports.ssl_match_hostname
.M2Crypto peut 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 de certificat.
Sur la base du commentaire de Glyphs, il semble que M2Crypto effectue une meilleure vérification de certificat par défaut que ce que vous pouvez faire avec pyOpenSSL actuellement, car M2Crypto vérifie également le champ subjectAltName.
J'ai également blogué sur la façon d' obtenir les certificats fournis par Mozilla Firefox en Python et utilisables avec les solutions SSL Python.
la source
Jython effectue la vérification des certificats par défaut, donc en utilisant des modules de bibliothèque standard, par exemple httplib.HTTPSConnection, etc., avec jython vérifiera les certificats et donnera des exceptions pour les échecs, c'est-à-dire les identités incompatibles, les certificats expirés, etc.
En fait, vous devez faire un travail supplémentaire pour que jython se comporte comme cpython, c'est-à-dire pour que jython ne vérifie PAS les certificats.
J'ai écrit un article de blog sur la façon de désactiver la vérification des certificats sur jython, car cela peut être utile dans les phases de test, etc.
Installation d'un fournisseur de sécurité de confiance totale sur java et jython.
http://jython.xhaus.com/installing-an-all-trusting-security-provider-on-java-and-jython/
la source
Le code suivant vous permet de bénéficier de tous les contrôles de validation SSL (ex: validité de la date, chaîne de certificats CA ...) SAUF une étape de vérification enfichable par ex. Pour vérifier le nom d'hôte ou faire d'autres étapes de vérification de certificat 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()
la source
pyOpenSSL est une interface avec la bibliothèque OpenSSL. Il devrait fournir tout ce dont vous avez besoin.
la source
J'avais le même problème mais je voulais minimiser les dépendances tierces (car ce script unique devait être exécuté par de nombreux utilisateurs). Ma solution était de boucler un
curl
appel et de m'assurer que le code de sortie était0
. A travaillé comme un charme.la source