Verwendung von Pythons pyOpenSSL zur Überprüfung von SSL-Zertifikaten, die von einem Host heruntergeladen wurden
Ab November 2020 kann die Chain of Trust ohne Aufruf von OpenSSL mit Python's subprocess verifiziert werden.
Während ich ein Skript schrieb, um zu prüfen, ob Websites korrekt auf 'https:/www.' umgeleitet werden. Ich dachte daran, auch einige SSL-Zertifikatsprüfungen hinzuzufügen. Das bedeutet, dass ich SSL-Zertifikate, die ich von einem Host heruntergeladen habe, überprüfen musste. Ist das Zertifikat wirklich für diese Website? Zeigen Sie mir das Ablaufdatum. Ist die Zertifikatskette korrekt? Und können wir dem/den Zertifikat(en) vertrauen?
Anfänglich blieb ich dort stecken, wo viele Leute wegen des Folgenden stecken bleiben. Die Zwischenzertifikate, die Sie von einem Host herunterladen, sind nicht vertrauenswürdig und pyOpenSSL hat bei der Überprüfung einer Zertifikatskette nicht das Flag 'untrusted' verwendet. Das bedeutet, dass das Zertifikat als vertrauenswürdig gekennzeichnet werden kann, obwohl es das nicht ist.
Die einzige Möglichkeit zur Umgehung bestand darin, den Befehl OpenSSL verify mit subprocess auszuführen. Nicht das, was wir wollen, aber zumindest konnten wir es tun. Es gibt andere Möglichkeiten, aber sie sind viel komplexer. Dies wurde 2016 berichtet und diskutiert. Mit dem Release von pyOpenSSL 20.0.0 (2020-11-27) wurden folgende Änderungen vorgenommen:
- eine neue Methode 'load_locations()' zu X509Store, um vertrauenswürdige Zertifikats-Dateibündel und/oder Verzeichnisse zu setzen
- ein neuer Parameter 'chain' zu X509StoreContext, mit dem Sie untrusted -Zertifikate hinzufügen können
- eine neue Methode 'get_verified_chain()' zuX509StoreContext, die die komplette verifizierte Kette zurückgibt
Mit diesen Änderungen können wir nun endlich die Chain of Trust verifizieren.
Python und Kryptographie ist nicht einfach
Websites, Dienste sind von HTTP auf HTTPS umgestiegen. Das bedeutet, wenn Sie sich mit einem Dienst verbinden, müssen Sie auch das/die Zertifikat(e) überprüfen. Tut Python dies für Sie? Der Python requests library tut dies automatisch für Sie. Sie müssen nicht einmal einen Parameter wie 'verify=True' hinzufügen.
Aber hier suche ich nach einer Möglichkeit, die SSL-Zertifikate in meinem eigenen Python -Skript zu überprüfen. Im Folgenden beschreibe ich einige Möglichkeiten, dies zu tun, und einen Python -Code, den ich geschrieben habe, um dies zu untersuchen. Dies läuft auf meinem Ubuntu 18.04 PC.
Relevantes Lesen, Anschauen
Vielleicht möchten Sie damit beginnen, die Artikel 'Chain of Fools: An Exploration of Certificate Chain Validation Mishaps' und '[Cryptography-dev] on how (not) to chain certs with openssl + pyopenssl' zu lesen und ein schönes Video 'Digital Certificates: Chain of Trust' anzusehen, siehe Links unten.
Die Klasse CertInfo
In den folgenden Beispielen verwende ich die Klasse CertInfo(). Dies ist eine Klasse, die ich geschrieben habe, um Informationen aus einem Zertifikat zu extrahieren. Der Python -Code befindet sich am Ende dieses Artikels.
Die Zertifikatskette
Eine Zertifikatskette ist eine verknüpfte Liste von Zertifikaten. In jedem Zertifikat gibt es zwei Elemente, die angeben, wie sie verknüpft sind:
- Subject-CN (allgemeiner Name)
- Issuer-CN (allgemeiner Name)
Beginnend mit dem Server-Zertifikat, wird es vom Issuer-CN ausgestellt. Das Server-Zertifikat wird auch Endteilnehmer-Zertifikat, Blatt-Zertifikat oder Teilnehmer-Zertifikat genannt. Das nächste heruntergeladene Zertifikat muss eine Subject-CN haben, die mit der Issuer-CN des Server-Zertifikats identisch ist, usw.
Im Folgenden ist das erste vom Host heruntergeladene Zertifikat host_cert[0], das zweite host_cert[1], usw. Das letzte Zertifikat ist das Stammzertifikat root_cert.
Example#1: www.badssl.com
INFO - ------------------------------------------------------------
INFO - Dumping certs: www.badssl.com:443 ...
INFO - * got 2 certs from host
INFO - * got root_cert
INFO - host_cert[0]
INFO - * Subject-CN: *.badssl.com
INFO - * Issuer-CN: DigiCert SHA2 Secure Server CA
INFO - host_cert[1]
INFO - * Subject-CN: DigiCert SHA2 Secure Server CA
INFO - * Issuer-CN: DigiCert Global Root CA
INFO - root_cert
INFO - * Subject-CN: DigiCert Global Root CA
INFO - * Issuer-CN: DigiCert Global Root CA
Beispiel#2: two-intermediate-certs-example.org
INFO - ------------------------------------------------------------
INFO - Dumping certs: two-intermediate-certs-example.org:443 ...
INFO - * got 3 certs from host
INFO - * got root_cert
INFO - host_cert[0]
INFO - * Subject-CN: two-intermediate-certs-example.org
INFO - * Issuer-CN: Sectigo RSA Domain Validation Secure Server CA
INFO - host_cert[1]
INFO - * Subject-CN: Sectigo RSA Domain Validation Secure Server CA
INFO - * Issuer-CN: USERTrust RSA Certification Authority
INFO - host_cert[2]
INFO - * Subject-CN: USERTrust RSA Certification Authority
INFO - * Issuer-CN: AAA Certificate Services
INFO - root_cert
INFO - * Subject-CN: AAA Certificate Services
INFO - * Issuer-CN: AAA Certificate Services
Example#3: www.example.org
INFO - ------------------------------------------------------------
INFO - Dumping certs: www.example.org:443 ...
INFO - * got 3 certs from host
INFO - * got root_cert
INFO - host_cert[0]
INFO - * Subject-CN: www.example.org
INFO - * Issuer-CN: DigiCert TLS RSA SHA256 2020 CA1
INFO - host_cert[1]
INFO - * Subject-CN: DigiCert TLS RSA SHA256 2020 CA1
INFO - * Issuer-CN: DigiCert Global Root CA
INFO - host_cert[2]
INFO - * Subject-CN: DigiCert Global Root CA
INFO - * Issuer-CN: DigiCert Global Root CA
INFO - root_cert
INFO - * Subject-CN: DigiCert Global Root CA
INFO - * Issuer-CN: DigiCert Global Root CA
Mit diesen Daten können wir überprüfen, ob die Kette, die beim Server-Zertifikat beginnt, beim Root-Zertifikat endet. Wenn wir überprüfen wollen, ob die Zertifikate korrekt konfiguriert sind, müssen wir dies unbedingt tun, da OpenSSL diese Reihenfolge nicht überprüft! Beachten Sie, dass www.badssl.com ein Zwischenzertifikat hat und two-intermediate-certs-example.org und www.example.org zwei Zwischenzertifikate haben. www.example.org ist ein Sonderfall, auf den ich weiter unten eingehen werde.
Hier ist der Code von Python , um die Zertifikatskette zu überprüfen:
def check_chain_order(self):
if self.root_cert is None:
return False
# link issuer-subject, start with server_cert
for i, cert in enumerate(self.host_certs):
if i < 1:
# we need two
continue
ci1 = CertInfo(self.host_certs[i - 1])
ci2 = CertInfo(self.host_certs[i])
if ci1.issuer_cn != ci2.subject_cn:
return False
# link issuer-subject, root_cert
ci1 = CertInfo(self.host_certs[-1])
ci2 = CertInfo(self.root_cert)
if ci1.issuer_cn != ci2.subject_cn:
return False
return True
Abrufen des Stammzertifikats
Um die Zertifikatskette zu überprüfen, benötigen wir auch das Wurzelzertifikat. In vielen Fällen können Sie einen CA root -Pfad zu einem Verzeichnis auf Ihrem Gerät angeben und die Verifizierungsfunktion wird das Wurzelzertifikat automatisch nachschlagen. Auf meinem PC kann ich den Parameter OpenSSL CApath auf setzen:
/etc/ssl/certs
Aber was ist, wenn wir das CA root -Zertifikat für die Verwendung in der Zertifikatsketten-Verifizierungsfunktion wie oben beschrieben erhalten wollen? Ich habe nicht gesehen, wie man das mit pyOpenSSL machen kann, aber es kann auf andere Weise gemacht werden.
Erstens können wir dies mit OpenSSL und subprocess tun. Der Befehl OpenSSL ist:
openssl x509 -noout -issuer_hash -in cert.pem
wobei cert.pm das letzte (Zwischen-)Zertifikat ist, das vom Host geholt wurde (vorausgesetzt, die Zertifikatsreihenfolge ist korrekt). Es gibt einige Einschränkungen, aber in den meisten Fällen wird dies funktionieren. Dies gibt eine hexadezimale Zahl wie zurück:
4a6481c9
Mit einem angehängten '.0' ist dies ein symlink für das Stammzertifikat:
ls -l /etc/ssl/certs/4a6481c9.0
Ergebnis:
lrwxrwxrwx 1 root root 27 okt 14 2017 /etc/ssl/certs/4a6481c9.0 -> GlobalSign_Root_CA_-_R2.pem
Wenn Sie wissen wollen, warum das so funktioniert, sehen Sie sich das Dienstprogramm c_rehash an, das mit OpenSSL geliefert wird. Dieses erstellt einen Hash zum schnellen Nachschlagen von Wurzelzertifikaten.
In Python verwende ich subprocess , um den OpenSSL -Befehl auszuführen, und schreibe dann die Root-Datei in mein Verzeichnis für den einfachen Zugriff. Beachten Sie, dass ich STDIN verwende, um PEM mit OpenSSL zu füttern und STDOUT verwende, um das Ergebnis zu erfassen.
def get_root_cert(self, pem):
cmd = ['openssl', 'x509', '-noout', '-issuer_hash']
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
out = p.communicate(input=pem.encode())[0].decode('utf-8').strip()
if p.returncode != 0:
return None
root_pem_file = os.path.join(os.sep, 'etc', 'ssl', 'certs', out + '.0')
if not os.path.isfile(root_pem_file):
return None
with open(root_pem_file, 'r') as fh:
root_pem = fh.read()
with open('root.pem', 'w') as fh:
fh.write(root_pem)
return crypto.load_certificate(crypto.FILETYPE_PEM, root_pem)
Eine andere Möglichkeit, das Wurzelzertifikat zu erhalten, wenn wir ein Wurzelzertifikat-Bundle auf unserem System haben, besteht darin, dieses Bundle herunterzuladen, zum Beispiel von der Website Curl :
https://curl.haxx.se/docs/caextract.html
Diese Datei enthält das CA root PEMs. Wenn wir diese verwenden wollen, sollten wir die Zertifikate extrahieren und dann ein Wörterbuch mit dem (Index-)Schlüssel Subject-CN und dem Wert PEM erstellen. Beim ersten Mal erstellen und speichern wir dies in einer Datei (mit Pickle), beim nächsten Mal laden wir diese Datei, indizieren das Verzeichnis und geben die PEM des Zertifikats CA root zurück.
def get_root_cert(self, pem):
root_subject_cn2pems_file = 'root_subject_cn2pems.pickle'
cacert_file = 'cacert.pem'
cert = crypto.load_certificate(crypto.FILETYPE_PEM, pem)
ci = CertInfo(cert)
issuer_cn = ci.issuer_cn
if os.path.isfile(root_subject_cn2pems_file):
# use stored root pems
with open(root_subject_cn2pems_file, 'rb') as fh:
root_subject_cn2pems = pickle.load(fh)
else:
# create root pems and store
with open(cacert_file, 'r') as fh:
capems = fh.read()
pem_begin = '-----BEGIN CERTIFICATE-----'
pem_end = '-----END CERTIFICATE-----'
root_subject_cn2pems = {}
for part in capems.split(pem_begin)[1:]:
if pem_end not in part:
continue
pem_part, rem = part.split(pem_end, 1)
pem = str(pem_begin + pem_part + pem_end)
cert = crypto.load_certificate(crypto.FILETYPE_PEM, pem)
ci = CertInfo(cert)
if ci.subject_cn is None:
continue
root_subject_cn2pems[ci.subject_cn] = pem
with open(root_subject_cn2pems_file, 'wb') as fh:
pickle.dump(root_subject_cn2pems, fh)
# try to get root pem
if issuer_cn not in root_subject_cn2pems:
return None
root_pem = root_subject_cn2pems[issuer_cn]
with open('root.pem', 'w') as fh:
fh.write(root_pem)
return crypto.load_certificate(crypto.FILETYPE_PEM, root_pem)
Wiederum ist CertInfo() eine Klasse, die ich geschrieben habe, um Informationen aus einem Zertifikat zu extrahieren. Der Python -Code befindet sich am Ende dieses Artikels.
Überprüfen der Vertrauenskette
Es reicht nicht aus, zu prüfen, ob das Server-Zertifikat mit einem Root-Zertifikat auf Ihrem PC, Telefon, Gerät verknüpft ist. Es gibt auch die so genannte "Chain of Trust". Zertifikaten, die wir von einem Host herunterladen, kann nicht vertraut werden. Das einzige Zertifikat, dem vertraut werden kann, ist das Stammzertifikat, das sich auf Ihrem Gerät, PC, in einem speziellen Verzeichnis, auf meinem PC Ubuntu befindet:
/etc/ssl/certs
Mit dem Root-Zertifikat können wir prüfen, ob wir dem nächsten (Zwischen-)Zertifikat vertrauen können. Wenn es vertrauenswürdig ist, verwenden wir dieses Zwischenzertifikat und prüfen, ob das nächste Zertifikat vertrauenswürdig ist. Das machen wir so lange, bis wir beim Server-Zertifikat ankommen.
Der Befehl OpenSSL für die Website www.badssl.com :
openssl verify -x509_strict -CApath /etc/ssl/certs -untrusted 1.pem 0.pem
Wenn wir das Root-Zertifikat haben, können wir das tun:
openssl verify -x509_strict -no-CApath -CAfile root.pem -untrusted 1.pem 0.pem
Das 'untrusted'-Flag sagt OpenSSL , dass 1.pem nicht vertrauenswürdig ist und vor der Überprüfung von 0.pem vertrauenswürdig sein muss. In ähnlicher Weise können wir die Kette für die Website two-intermediate-certs-example.org prüfen, die zwei Zwischenzertifikate hat:
openssl verify -x509_strict -no-CApath -CAfile root.pem -untrusted 2.pem -untrusted 1.pem 0.pem
Beachten Sie, dass, wenn Sie die Zwischenzertifikate 1.pem und 2.pem für die Website two-intermediate-certs-example.org vertauschen, das Ergebnis in beiden Fällen das gleiche ist. Das bedeutet, dass OpenSSL zuerst versucht, die Kettenreihenfolge zu finden und dann die Vertrauensoperation startet. In diesem Fall können wir auch die beiden Zwischenzertifikate in 12.pem verketten und ausführen:
openssl verify -x509_strict -no-CApath -CAfile root.pem -untrusted 12.pem 0.pem
Die SSL-Zertifikate, die ich vom Host heruntergeladen habe, sind in self.host_certs und auch in den Dateien 0.pem, 1.pem, ... Mit Pythons subprocess ist der Code:
def chain_is_trusted(self):
cmd = ['openssl', 'verify', '-x509_strict', '-no-CApath', '-CAfile', 'root.pem']
for i in range(self.host_certs_len - 1, 0, -1):
cmd.extend([
'-untrusted',
str(i) + '.pem',
])
cmd.extend([
'0.pem',
])
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
out = p.communicate()[0].decode('utf-8').strip()
if p.returncode == 0:
return True
return False
Dies ist hässlich und erfordert, dass wir zuerst PEM -Dateien speichern, bevor wir subprocess aufrufen.
Seit pyOpenSSL 20.0.0 können wir das viel eleganter machen:
def chain_is_trusted(self):
store = crypto.X509Store()
store.set_flags(crypto.X509StoreFlags.X509_STRICT)
store.load_locations(None, capath='/etc/ssl/certs')
# server cert
server_cert = self.host_certs[0]
# intermediate certs
untrusted_certs = self.host_certs[1:]
store_ctx = crypto.X509StoreContext(store, server_cert, chain=untrusted_certs)
try:
store_ctx.verify_certificate()
# optional
# certs = store_ctx.get_verified_chain()
return True
except crypto.X509StoreContextError as e:
pass
return False
Die Methode verify_certificate() erzeugt eine Exception, wenn die Kette nicht verifiziert werden kann.
Die Vertrauenskette, Schritt für Schritt
Oben haben wir die Chain of Trust mit einem einzigen OpenSSL -Befehl überprüft. Wir können dies aber auch auf andere Weise tun. Ausgehend von dem vertrauenswürdigen Root-Zertifikat auf meinem PC versuchen wir zunächst, dem letzten Zwischenzertifikat des Hosts zu vertrauen. Wenn dies gelingt, verwenden wir das nun vertrauenswürdige Zwischenzertifikat, um zu versuchen, dem nächsten untrusted -Zertifikat zu vertrauen, bis hin zum Server-Zertifikat. Bei OpenSSL verwenden wir das Flag 'partial_chain'.
Zum Beispiel für die Website two-intermediate-certs-example.org können wir folgende Befehle ausgeben:
openssl verify -x509_strict -no-CApath -CAfile root.pem -partial_chain 2.pem
openssl verify -x509_strict -no-CApath -CAfile 2.pem -partial_chain 1.pem
openssl verify -x509_strict -no-CApath -CAfile 1.pem -partial_chain 0.pem
Selbstsignierte (Zwischen-)Zertifikate
Wenn ein Zwischenzertifikat selbstsigniert ist, stoppt OpenSSL die Validierung(?). Das heißt, wir wollen prüfen, ob ein vom Host heruntergeladenes Zertifikat selbstsigniert ist.
Ein Zertifikat ist selbstsigniert, wenn:
- die Subject-CN und die Issuer-CN übereinstimmen
- die subjectKeyIdentifier und die authorityKeyIdentifier übereinstimmen
- das Zertifikat enthält eine Schlüsselverwendungserweiterung mit dem gesetzten Bit KU_KEY_CERT_SIGN
Ich habe nur die ersten beiden Punkte implementiert, keine Ahnung, wie ich den dritten Punkt umsetzen soll.
Beim Testen fand ich für self-signed.badssl.com:
subjectKeyIdentifier: None
authorityKeyIdentifier: None
aber für www.example.org:
subjectKeyIdentifier: 03:DE:50:35:56:D1:4C:BB:66:F0:A3:E2:1B:1B:C3:97:B2:3D:D1:55
authorityKeyIdentifier: keyid:03:DE:50:35:56:D1:4C:BB:66:F0:A3:E2:1B:1B:C3:97:B2:3D:D1:55
Ich nehme an, dass ich hier das führende 'keyid:' entfernen muss, bevor ich das subjectKeyIdentifier mit dem authorityKeyIdentifier vergleiche.
def cert_is_self_signed(self, cert):
ci = CertInfo(cert)
# strip optional keyid from authority_key_identifier
keyid = 'keyid:'
keyid_len = len(keyid)
extension_authority_key_identifier = ci.extension_authority_key_identifier
if extension_authority_key_identifier is not None:
if extension_authority_key_identifier[:keyid_len] == keyid:
extension_authority_key_identifier = extension_authority_key_identifier[keyid_len:]
# subject_key_identifier, authority_key_identifier: both None or match
if (ci.subject_cn == ci.issuer_cn) and \
((ci.extension_subject_key_identifier is None and ci.extension_authority_key_identifier is None) or \
(ci.extension_subject_key_identifier == extension_authority_key_identifier)):
return True
return False
Mehr über selbstsignierte Zertifikate finden Sie im Artikel 'How to know if certificate is self-signed', siehe Links unten.
Hier wird auch auf RFC 3280 verwiesen:
'Ein Zertifikat ist selbstausgestellt, wenn die DNs, die im Subject- und Issuer-Feld erscheinen, identisch sind und nicht leer sind. Im Allgemeinen sind der Aussteller und der Betreff der Zertifikate, die einen Pfad bilden, für jedes Zertifikat unterschiedlich. Eine CA kann jedoch ein Zertifikat für sich selbst ausstellen, um Key-Rollover oder Änderungen in den Zertifikatsrichtlinien zu unterstützen. Diese selbst ausgestellten Zertifikate werden bei der Auswertung von Pfadlängen- oder Namensbeschränkungen nicht mitgezählt.'
Das keyIdentifier-Feld der authorityKeyIdentifier -Erweiterung MUSS in allen von konformen CAs generierten Zertifikaten enthalten sein, um den Aufbau von Zertifizierungspfaden zu erleichtern. Es gibt eine Ausnahme: Wenn eine CA ihren öffentlichen Schlüssel in Form eines "selbstsignierten" Zertifikats ausgibt, KANN die Kennung des Behördenschlüssels weggelassen werden. Die Signatur eines selbstsignierten Zertifikats wird mit dem privaten Schlüssel erzeugt, der mit dem öffentlichen Schlüssel des Zertifikats verknüpft ist. (Dies beweist, dass der Aussteller sowohl den öffentlichen als auch den privaten Schlüssel besitzt.) In diesem Fall wären die Subject- und Authority-Key-Identifikatoren identisch, aber nur der Subject-Key-Identifikator wird für die Erstellung des Zertifizierungspfads benötigt.'
Verwechslung mit example.com, example.org
Bei Tests habe ich auch example.com und example.org verwendet. Der Host gab drei Zertifikate zurück. Ich überprüfte das Ergebnis, indem ich die Zertifikate ersetzte und sah, was passiert.
openssl verify -x509_strict -no-CApath -CAfile root.pem -untrusted 2.pem -untrusted 1.pem 0.pem
Es war ein Durcheinander. Ich konnte das Zwischenzertifikat 2.pem entfernen und OpenSSL sagte immer noch: OK. Ich konnte 1.pem durch ein anderes zufälliges Zertifikat ersetzen und OpenSSL sagte immer noch: OK. Was war hier los?
Dann schaute ich mir die vom Host zurückgegebenen Zertifikate an und stellte fest, dass das dritte Zertifikat, 2.pem, selbstsigniert und mit dem Stammzertifikat auf meinem PC identisch war. Das bedeutet, dass diese Domänen mir ein Root-Zertifikat senden? OpenSSL beschwert sich nicht, die Browser beschweren sich auch nicht.
Ich habe das nicht weiter untersucht, aber ich dachte, ich sollte das erwähnen, um Kopfschmerzen zu vermeiden ... :-(
Die Falsche-Host-Prüfung
Die Falsche-Host-Prüfung muss sicherstellen, dass das Zertifikat für diesen Host ist. Ich habe nicht gesehen, wie dies mit pyOpenSSL gemacht werden kann, in der Tat habe ich keine Möglichkeit gesehen, dies mit OpenSSL zu tun. OpenSSL warnt Sie nicht, wenn das Zertifikat für einen anderen Host ist.
Wir können Wget verwenden:
wget wrong.host.badssl.com
Ergebnis:
ERROR: no certificate subject alternative name matches requested host name ‘wrong.host.badssl.com’.
Oder wir können Curl verwenden:
curl -L wrong.host.badssl.com
Ergebnis:
curl: (51) SSL: no alternative certificate subject name matches target host name 'wrong.host.badssl.com'
In Python können wir das mit der Python requests library machen. Ein falscher Host gibt die Ausnahme:
...
request.get(...)
...
Ergebnis:
HTTPSConnectionPool(host='wrong.host.badssl.com', port=443): Max retries exceeded with url: / (Caused by SSLError(CertificateError("hostname 'wrong.host.badssl.com' doesn't match either of '*.badssl.com', 'badssl.com'",),))
Beachten Sie, dass die Exception 'hostname ... doesn't match' nur erscheint, wenn die anderen Tests erfolgreich sind. Wenn es irgendwo ein Problem mit den Zertifikaten gibt, erhalten Sie eine Exception 'certificate verify failed'.
Die Klasse CertInfo()
Ich habe etwas Python -Code geschrieben, um damit zu spielen. Am wichtigsten ist die Klasse CertInfo(). Hier dekodiere ich das Zertifikat.
# cert_info.py
import datetime
from OpenSSL import crypto
class CertInfo:
def __init__(
self,
cert=None,
):
self.cert = cert
def decode_x509name_obj(self, o):
parts = []
for c in o.get_components():
parts.append(c[0].decode('utf-8') + '=' + c[1].decode('utf-8'))
return ', '.join(parts)
def cert_date_to_gmt_date(self, d):
return datetime.datetime.strptime(d.decode('ascii'), '%Y%m%d%H%M%SZ')
def cert_date_to_gmt_date_string(self, d):
return self.cert_date_to_gmt_date(d).strftime("%Y-%m-%d %H:%M:%S GMT")
def get_item(self, item, extension=None, return_as=None, algo=None):
try:
if item == 'subject':
return self.decode_x509name_obj(self.cert.get_subject())
elif item == 'subject_o':
return self.cert.get_subject().O.strip()
elif item == 'subject_cn':
return self.cert.get_subject().CN.strip()
elif item == 'extensions':
ext_count = self.cert.get_extension_count()
if extension is None:
ext_infos = []
for i in range (0, ext_count):
ext = self.cert.get_extension(i)
ext_infos.append(ext.get_short_name().decode('utf-8'))
return ext_infos
for i in range (0, ext_count):
ext = self.cert.get_extension(i)
if extension in str(ext.get_short_name()):
return ext.__str__().strip()
return None
elif item == 'version':
return self.cert.get_version()
elif item == 'pubkey_type':
pk_type = self.cert.get_pubkey().type()
if pk_type == crypto.TYPE_RSA:
return 'RSA'
elif pk_type == crypto.TYPE_DSA:
return 'DSA'
return 'Unknown'
elif item == 'pubkey_pem':
return crypto.dump_publickey(crypto.FILETYPE_PEM, self.cert.get_pubkey()).decode('utf-8')
elif item == 'serial_number':
return self.cert.get_serial_number()
elif item == 'not_before':
not_before = self.cert.get_notBefore()
if return_as == 'string':
return self.cert_date_to_gmt_date_string(not_before)
return self.cert_date_to_gmt_date(not_before)
elif item == 'not_after':
not_after = self.cert.get_notAfter()
if return_as == 'string':
return self.cert_date_to_gmt_date_string(not_after)
return self.cert_date_to_gmt_date(not_after)
elif item == 'has_expired':
return self.cert.has_expired()
elif item == 'issuer':
return self.decode_x509name_obj(self.cert.get_issuer())
elif item == 'issuer_o':
return self.cert.get_issuer().O.strip()
elif item == 'issuer_cn':
return self.cert.get_issuer().CN.strip()
elif item == 'signature_algorithm':
return self.cert.get_signature_algorithm().decode('utf-8')
elif item == 'digest':
# ['md5', 'sha1', 'sha256', 'sha512']
return self.cert.digest(algo)
elif item == 'pem':
return crypto.dump_certificate(crypto.FILETYPE_PEM, self.cert).decode('utf-8')
else:
return None
except Exception as e:
logger.error('item = {}, exception, e = {}'.format(item, e))
return None
@property
def subject(self):
return self.get_item('subject')
@property
def subject_o(self):
return self.get_item('subject_o')
@property
def subject_cn(self):
return self.get_item('subject_cn')
@property
def subject_name_hash(self):
return self.get_item('subject_name_hash')
@property
def extension_count(self):
return self.get_item('extension_count')
@property
def extensions(self):
return self.get_item('extensions')
@property
def extension_basic_constraints(self):
return self.get_item('extensions', extension='basicConstraints')
@property
def extension_subject_key_identifier(self):
return self.get_item('extensions', extension='subjectKeyIdentifier')
@property
def extension_authority_key_identifier(self):
return self.get_item('extensions', extension='authorityKeyIdentifier')
@property
def extension_subject_alt_name(self):
return self.get_item('extensions', extension='subjectAltName')
@property
def version(self):
return self.get_item('version')
@property
def pubkey_type(self):
return self.get_item('pubkey_type')
@property
def pubkey_pem(self):
return self.get_item('pubkey_pem')
@property
def serial_number(self):
return self.get_item('serial_number')
@property
def not_before(self):
return self.get_item('not_before')
@property
def not_before_s(self):
return self.get_item('not_before', return_as='string')
@property
def not_after(self):
return self.get_item('not_after')
@property
def not_after_s(self):
return self.get_item('not_after', return_as='string')
@property
def has_expired(self):
return self.get_item('has_expired')
@property
def issuer(self):
return self.get_item('issuer')
@property
def issuer_o(self):
return self.get_item('issuer_o')
@property
def issuer_cn(self):
return self.get_item('issuer_cn')
@property
def signature_algorithm(self):
return self.get_item('signature_algorithm')
@property
def digest_sha256(self):
return self.get_item('digest', algo='sha256')
@property
def pem(self):
return self.get_item('pem')
Nicht alle Eigenschaften des Zertifikats sind verfügbar. Zum Beispiel habe ich keinen einfachen Weg gefunden, um die Signatur zu erhalten.
Um die Elemente des Zertifikats zu drucken:
def print_cert_items(self, cert_id, cert):
def format_cert_items(m):
return '{}: {}'.format(m[0], m[1])
ci = CertInfo(cert)
cert_items = [
('Subject', ci.subject),
('Subject-CN', ci.subject_cn),
('Subject name hash', ci.subject_name_hash),
('Issuer', ci.issuer),
('Issuer-CN', ci.issuer_cn),
('Extensions', ci.extensions),
('Extension-basicConstraints', ci.extension_basic_constraints),
('Extension-subjectKeyIdentifier', ci.extension_subject_key_identifier),
('Extension-authorityKeyIdentifier', ci.extension_authority_key_identifier),
('Extension-subjectAltName (SAN)', ci.extension_subject_alt_name),
('Version', ci.version),
('Serial_number', ci.serial_number),
('Public key-type', ci.pubkey_type),
('Public key-pem', ci.pubkey_pem),
('Not before', ci.not_before_s),
('Not after', ci.not_after_s),
('Has expired', ci.has_expired),
('Signature algortihm', ci.signature_algorithm),
('Digest-sha256', ci.digest_sha256),
('PEM', ci.pem),
]
print('{}'.format(cert_id))
cert_item_lines = map(format_cert_items, cert_items)
print('{}'.format('\n'.join(cert_item_lines)))
Zusammenfassung
Dies war ein langer Weg. Ich suchte nach einer Python pyOpenSSL only Lösung, um die Chain of Trust ohne subprocess zu verifizieren und stieß die ganze Zeit auf Seiten, die mir sagten, dass dies nicht mit pyOpenSSL only möglich ist. Daraufhin überprüfte ich das pyOpenSSL -Repository auf Github.com und es hieß, dass dies erst kürzlich hinzugefügt worden sei. Die changelog zeigte die Parameter und Methoden und nach dem Lesen der Docs war es einfach zu implementieren.
Ich fand auch Möglichkeiten, die Zertifikatskette zu prüfen, ob ein Zertifikat selbstsigniert oder abgelaufen ist und eine Möglichkeit, auf einen falschen Host zu prüfen. Jetzt geht es an die Erweiterung meines Server-Check-Skripts ...
Links / Impressum
[Cryptography-dev] on how (not) to chain certs with openssl + pyopenssl
https://mail.badssl.com/pipermail/cryptography-dev/2016-August/000676.html
certvalidator
https://github.com/wbond/certvalidator
Chain of Fools: An Exploration of Certificate Chain Validation Mishaps
https://duo.com/labs/research/chain-of-fools
Cheat Sheet - OpenSSL
https://megamorf.gitlab.io/cheat-sheets/openssl/
Digital Certificates: Chain of Trust
https://www.youtube.com/watch?v=heacxYUnFHA
Get or build PEM certificate chain in Python
https://stackoverflow.com/questions/51039393/get-or-build-pem-certificate-chain-in-python
Get your certificate chain right
https://medium.com/@superseb/get-your-certificate-chain-right-4b117a9c0fce
How to know if certificate is self-signed
https://security.stackexchange.com/questions/93162/how-to-know-if-certificate-is-self-signed/93163
How to validate / verify an X509 Certificate chain of trust in Python?
https://stackoverflow.com/questions/30700348/how-to-validate-verify-an-x509-certificate-chain-of-trust-in-python
PyOpenSSL - how can I get SAN(Subject Alternative Names) list
https://stackoverflow.com/questions/49491732/pyopenssl-how-can-i-get-sansubject-alternative-names-list
ssl-check.py
https://gist.github.com/gdamjan/55a8b9eec6cf7b771f92021d93b87b2c
Use openssl to individually verify components of a certificate chain
https://security.stackexchange.com/questions/118062/use-openssl-to-individually-verify-components-of-a-certificate-chain
Verify a certificate chain using openssl verify
https://stackoverflow.com/questions/25482199/verify-a-certificate-chain-using-openssl-verify
X509StoreContext objects
https://www.pyopenssl.org/en/stable/api/crypto.html#x509storecontext-objects
Mehr erfahren
Cryptography pyOpenSSL
Einen Kommentar hinterlassen
Kommentieren Sie anonym oder melden Sie sich zum Kommentieren an.
Kommentare (2)
Eine Antwort hinterlassen
Antworten Sie anonym oder melden Sie sich an, um zu antworten.
Hello,
Very interesting. Could you share full script/class? It looks truncated.
Regards!
This example is not complete. So I re-iterate the previous comment. Please could you share the full script/class?
Neueste
- Ausblenden der Primärschlüssel der Datenbank UUID Ihrer Webanwendung
- Don't Repeat Yourself (DRY) mit Jinja2
- SQLAlchemy, PostgreSQL, maximale Anzahl von Zeilen pro user
- Anzeige der Werte in den dynamischen Filtern SQLAlchemy
- Sichere Datenübertragung mit Public Key Verschlüsselung und pyNaCl
- rqlite: eine hochverfügbare und distverteilte SQLite -Alternative
Meistgesehen
- Verwendung von Pythons pyOpenSSL zur Überprüfung von SSL-Zertifikaten, die von einem Host heruntergeladen wurden
- Verwendung von UUIDs anstelle von Integer Autoincrement Primary Keys mit SQLAlchemy und MariaDb
- Verbindung zu einem Dienst auf einem Docker -Host von einem Docker -Container aus
- PyInstaller und Cython verwenden, um eine ausführbare Python-Datei zu erstellen
- SQLAlchemy: Verwendung von Cascade Deletes zum Löschen verwandter Objekte
- Flask RESTful API Validierung von Anfrageparametern mit Marshmallow-Schemas