Muszę napisać skrypt, który łączy się z wieloma witrynami w naszym firmowym intranecie za pośrednictwem protokołu HTTPS i sprawdza, czy ich certyfikaty SSL są ważne; że nie wygasły, że zostały wydane dla prawidłowego adresu itp. Korzystamy z własnego wewnętrznego, korporacyjnego urzędu certyfikacji dla tych witryn, więc mamy klucz publiczny CA do weryfikacji certyfikatów.
Python domyślnie po prostu akceptuje i używa certyfikatów SSL podczas korzystania z HTTPS, więc nawet jeśli certyfikat jest nieważny, biblioteki Pythona, takie jak urllib2 i Twisted, będą po prostu szczęśliwie korzystać z certyfikatu.
Czy jest gdzieś dobra biblioteka, która pozwoli mi połączyć się z witryną przez HTTPS i zweryfikować w ten sposób jej certyfikat?
Jak zweryfikować certyfikat w Pythonie?
źródło
treq
lubtwisted.web.client.Agent
od wersji 14.0, Twisted domyślnie weryfikuje certyfikaty.Odpowiedzi:
Począwszy od wersji 2.7.9 / 3.4.3, Python domyślnie próbuje przeprowadzić weryfikację certyfikatu.
Zostało to zaproponowane w PEP 467, z którym warto przeczytać: https://www.python.org/dev/peps/pep-0476/
Zmiany dotyczą wszystkich odpowiednich modułów stdlib (urllib / urllib2, http, httplib).
Odpowiednia dokumentacja:
https://docs.python.org/2/library/httplib.html#httplib.HTTPSConnection
https://docs.python.org/3/library/http.client.html#http.client.HTTPSConnection
Należy zauważyć, że nowa wbudowana weryfikacja jest oparta na bazie danych certyfikatów dostarczonej przez system . W przeciwieństwie do tego, pakiet żądań zawiera własny pakiet certyfikatów. Zalety i wady obu podejść omówiono w sekcji bazy danych zaufania w PEP 476 .
źródło
HTTPSConnection
klasy jest obowiązkowe ? UżywałemSSLSocket
. Jak mogę przeprowadzić walidację zSSLSocket
? Czy muszę wyraźnie potwierdzić użycie,pyopenssl
jak wyjaśniono tutaj ?Dodałem dystrybucję do Python Package Index, która sprawia, że
match_hostname()
funkcja zssl
pakietu Python 3.2 jest dostępna we wcześniejszych wersjach Pythona.http://pypi.python.org/pypi/backports.ssl_match_hostname/
Możesz go zainstalować za pomocą:
Lub możesz ustawić to jako zależność wymienioną w pliku
setup.py
. Tak czy inaczej, można go używać w następujący sposób: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: ...
źródło
getpeercert()
metody nazywanej tak, aby można było przekazać dane wyjściowematch_hostname()
.Możesz użyć Twisted do weryfikacji certyfikatów. Głównym interfejsem API jest CertificateOptions , które można podać jako
contextFactory
argument dla różnych funkcji, takich jak ListenSSL i startTLS .Niestety, ani Python, ani Twisted nie zawierają stosu certyfikatów CA wymaganych do faktycznego sprawdzenia poprawności HTTPS ani logiki walidacji HTTPS. Ze względu na ograniczenia w PyOpenSSL nie możesz jeszcze tego zrobić całkowicie poprawnie, ale dzięki temu, że prawie wszystkie certyfikaty zawierają temat commonName, możesz się wystarczająco zbliżyć.
Oto naiwna przykładowa implementacja weryfikującego klienta Twisted HTTPS, który ignoruje symbole wieloznaczne i rozszerzenia subjectAltName oraz używa certyfikatów urzędu certyfikacji obecnych w pakiecie „ca-Certificates” w większości dystrybucji Ubuntu. Wypróbuj to na swoich ulubionych ważnych i nieprawidłowych witrynach z certyfikatami :).
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()
źródło
URLPath(url).netloc
: oznacza to część hosta adresu URL przekazaną do secureGet. Innymi słowy, sprawdza, czy zwykła nazwa podmiotu jest taka sama, jak ta, o którą prosi dzwoniący.https://www.google.com/
ponieważ jednym z tematów jest „www.google.com”, co powoduje, że funkcja zwraca False. Prawdopodobnie miało to na celu zwrócenie True (zaakceptowane), jeśli nazwy pasują, a False, jeśli nie?PycURL robi to pięknie.
Poniżej znajduje się krótki przykład. Będzie to rzucić
pycurl.error
, jeśli coś jest podejrzany, gdzie można uzyskać krotki z kodem błędu i ludzkiej wiadomości czytelny.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()
Prawdopodobnie będziesz chciał skonfigurować więcej opcji, takich jak miejsce przechowywania wyników itp. Ale nie ma potrzeby zaśmiecania przykładu nieistotnymi.
Przykład wyjątków, które mogą zostać zgłoszone:
(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'")
Niektóre linki, które uznałem za przydatne, to libcurl-docs dla setopt i getinfo.
źródło
Lub po prostu ułatw sobie życie, korzystając z biblioteki żądań :
import requests requests.get('https://somesite.com', cert='/path/server.crt', verify=True)
Jeszcze kilka słów o jego zastosowaniu.
źródło
cert
Argumentem jest certyfikat po stronie klienta, a nie certyfikat serwera, aby sprawdzić przed. Chcesz użyćverify
argumentu.verify
argumentu, z wyjątkiem wyraźniejszego lub wyłączania weryfikacji.Oto przykładowy skrypt, który demonstruje walidację certyfikatu:
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()
źródło
CertValidatingHTTPSConnection.connect
. Zobacz to żądanie ściągnięcia, aby uzyskać szczegółowe informacje (i poprawkę).backports.ssl_match_hostname
.M2Crypto może przeprowadzić walidację . Jeśli chcesz, możesz również użyć M2Crypto z Twisted . Klient stacjonarny Chandler korzysta z Twisted do obsługi sieci i M2Crypto do SSL , w tym do weryfikacji certyfikatu.
Na podstawie komentarza Glyphs wydaje się, że M2Crypto domyślnie przeprowadza lepszą weryfikację certyfikatu niż to, co można obecnie zrobić z pyOpenSSL, ponieważ M2Crypto sprawdza również pole subjectAltName.
Napisałem również na blogu o tym, jak uzyskać certyfikaty, które Mozilla Firefox dostarcza w Pythonie i które można używać z rozwiązaniami Python SSL.
źródło
Jython domyślnie przeprowadza weryfikację certyfikatów, więc używając standardowych modułów bibliotecznych, np. Httplib.HTTPSConnection, itp. Z jythonem będzie weryfikować certyfikaty i dawać wyjątki w przypadku awarii, tj. Niezgodnych tożsamości, wygasłych certyfikatów itp.
W rzeczywistości musisz wykonać dodatkową pracę, aby jython zachowywał się jak cpython, tj. Aby jython NIE weryfikował certyfikatów.
Napisałem post na blogu o tym, jak wyłączyć sprawdzanie certyfikatów w jythonie, ponieważ może to być przydatne w fazach testowania itp.
Instalowanie zaufanego dostawcy zabezpieczeń w Javie i Jythonie.
http://jython.xhaus.com/installing-an-all-trusting-security-provider-on-java-and-jython/
źródło
Poniższy kod umożliwia korzystanie ze wszystkich sprawdzeń poprawności SSL (np. Ważność daty, łańcuch certyfikatów CA ...) Z WYJĄTKIEM podłączanego kroku weryfikacji, np. W celu weryfikacji nazwy hosta lub wykonania innych dodatkowych kroków weryfikacji certyfikatu.
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()
źródło
pyOpenSSL jest interfejsem do biblioteki OpenSSL. Powinien zapewnić wszystko, czego potrzebujesz.
źródło
Miałem ten sam problem, ale chciałem zminimalizować zależności stron trzecich (ponieważ ten jednorazowy skrypt miał być wykonywany przez wielu użytkowników). Moim rozwiązaniem było zawinięcie
curl
połączenia i upewnienie się, że kod zakończenia to0
. Działał jak urok.źródło