Met behulp van Python's pyOpenSSL om SSL-certificaten die van een host zijn gedownload te controleren
Vanaf november 2020 kan de vertrouwensketen worden geverifieerd zonder OpenSSL te bellen met Python's subprocess.
Tijdens het schrijven van een script om te controleren of websites correct zijn omgeleid naar 'https:/www'. Ik dacht om ook wat SSL-certificaatcontroles toe te voegen. Dit betekent dat ik SSL-certificaten die van een host zijn gedownload, moest controleren. Is het certificaat echt voor deze website? Laat me de vervaldatum zien. Is de certificaatketen correct? En kunnen we het certificaat (de certificaten) vertrouwen?
In eerste instantie kwam ik vast te zitten waar veel mensen vast kwamen te zitten door het volgende. De tussenliggende certificaten die u van een host downloadt zijn niet te vertrouwen en pyOpenSSL heeft de 'untrusted' vlag niet gebruikt bij de controle van een certificaatketen. Dit betekent dat het certificaat wel te vertrouwen is terwijl het dat niet is.
De enige manier om dit te doen was om het commando OpenSSL te verifiëren met behulp van subprocess. Niet wat we willen, maar we konden het tenminste doen. Er zijn andere manieren, maar die zijn veel complexer. Dit werd gerapporteerd en besproken in 2016. Met de release van pyOpenSSL 20.0.0 (2020-11-27) werden de volgende wijzigingen aangebracht:
- een nieuwe methode 'load_locations()' naar X509Store om trusted certificate file bundles en/of directories in te stellen
- een nieuwe parameter 'keten' aan X509StoreContext waar u untrusted -certificaten kunt toevoegen
- een nieuwe methode 'get_verified_chain()' naarX509StoreContext die de volledige gevalideerde keten retourneert
Met deze veranderingen kunnen we nu eindelijk de vertrouwensketen verifiëren.
Python en cryptografie is niet eenvoudig.
Websites, diensten zijn verhuisd van HTTP naar HTTPS. Dit betekent dat wanneer u verbinding maakt met een dienst, u ook het certificaat of de certificaten moet controleren. Doet Python dit voor u? De Python requests library doet dit automatisch voor u. Je hoeft niet eens een parameter als 'verify=True' toe te voegen.
Maar hier zoek ik naar een manier om de SSL-certificaten in mijn eigen Python script te controleren. Hieronder beschrijf ik enkele manieren om dit te doen en wat Python code die ik heb geschreven om dit te onderzoeken. Deze draait op mijn Ubuntu 18.04 PC.
Relevant lezen, kijken
Misschien wilt u beginnen met het lezen van de artikelen 'Chain of Fools: An Exploration of Certificate Chain Validation Mishaps', en '[Cryptography-dev] on how (not) to chain certs with openssl + pyopenssl', en het bekijken van een mooie video 'Digital Certificates: Chain of Trust', zie onderstaande links.
De CertInfo klasse
In de onderstaande voorbeelden gebruik ik een klasse CertInfo(). Dit is een klasse die ik heb geschreven om informatie uit een certificaat te halen. De code Python staat aan het einde van dit artikel.
De certificaatketen
Een certificatenketen is een gekoppelde lijst van certificaten. In elk certificaat staan twee items die aangeven hoe ze gekoppeld zijn:
- Subject-CN (gewone naam)
- Issuer-CN (gewone naam)
Te beginnen met het servercertificaat, wordt het afgegeven door de Issuer-CN. Het servercertificaat wordt ook wel end-entity-certificaat, bladcertificaat of abonneecertificaat genoemd. Het volgende certificaat dat we downloaden moet een Subject-CN hebben die identiek is aan het servercertificaat Issuer-CN, enz.
Hieronder is het eerste certificaat dat gedownload is van de host host_cert[0], het tweede host_cert[1], etc. Het laatste certificaat is het root-certificaat 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
Voorbeeld#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
Aan de hand van deze gegevens kunnen we controleren of de keten die begint bij het servercertificaat eindigt bij het basiscertificaat. Als we willen controleren of de certificaten correct zijn geconfigureerd, is het essentieel dat we dit doen omdat OpenSSL deze volgorde niet controleert! Merk op dat www.badssl.com één tussenliggend certificaat heeft en two-intermediate-certs-example.org en www.example.org twee tussenliggende certificaten hebben. www.example.org is een speciaal geval, ik zal dit hieronder bespreken.
Hier is Python code om de certificaatketen te controleren:
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
Het verkrijgen van het basiscertificaat
Om de certificaatketen te controleren hebben we ook het basiscertificaat nodig. In veel gevallen kunt u een CA root pad opnemen in een directory op uw apparaat en de verificatie-functie zal het root-certificaat automatisch opzoeken. Op mijn PC kan ik de OpenSSL CApath parameter instellen op:
/etc/ssl/certs
Maar wat als we het CA root certficaat willen krijgen voor gebruik in de certificaat ketenverificatie functie zoals hierboven beschreven? Ik heb niet gezien hoe ik dit kan doen met pyOpenSSL maar het kan op andere manieren.
Ten eerste kunnen we dit doen met OpenSSL en subprocess. Het commando OpenSSL is:
openssl x509 -noout -issuer_hash -in cert.pem
waarbij cert.pm het laatste (tussenliggende) certificaat is dat van de host wordt opgehaald (aangenomen dat de certificaatvolgorde correct is). Er zijn enkele beperkingen, maar in de meeste gevallen zal dit werken. Dit geeft een hexadecimaal getal als:
4a6481c9
Met een '.0' als bijlage is dit een symlink voor het basiscertificaat:
ls -l /etc/ssl/certs/4a6481c9.0
Resultaat:
lrwxrwxrwx 1 root root 27 okt 14 2017 /etc/ssl/certs/4a6481c9.0 -> GlobalSign_Root_CA_-_R2.pem
Als u wilt weten waarom dit zo werkt, zie dan het hulpprogramma c_rehash dat bij OpenSSL wordt geleverd. Dit bouwt een hash voor het snel opzoeken van root-certificaten.
In Python gebruik ik subprocess om het commando OpenSSL uit te voeren, en schrijf dan het rootbestand in mijn directory voor gemakkelijke toegang. Merk op dat ik STDIN gebruik om de PEM naar OpenSSL te voeren en STDOUT gebruik om het resultaat vast te leggen.
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)
Een andere manier om het hoofdcertificaat te verkrijgen als we een hoofdcertificaatbundel op ons systeem hebben, is om deze bundel te downloaden, bijvoorbeeld van de Curl website:
https://curl.haxx.se/docs/caextract.html
Dit bestand bevat de CA root PEMs. Als we dit willen gebruiken moeten we de certificaten uitpakken en vervolgens een woordenboek maken met (index)sleutel Subject-CN en de waarde PEM. De eerste keer dat we dit bouwen en opslaan in een bestand (met behulp van Pickle), de volgende keer dat we dit bestand laden, indexeren we de directory en geven we de PEM van het CA root certificaat terug.
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)
Ook hier is CertInfo() een klasse die ik heb geschreven om informatie uit een certificaat te halen. De code Python staat aan het einde van dit artikel.
Controle van de vertrouwensketen
Het is niet voldoende om te controleren of het servercertificaat gekoppeld is aan een root-certificaat op uw PC, telefoon, apparaat. Er is ook wat men noemt 'Chain of Trust'. Certificaten die we downloaden van een host zijn niet te vertrouwen. Het enige certificaat dat wel te vertrouwen is, is het root-certificaat dat op uw apparaat, PC, in een speciale directory staat, op mijn Ubuntu PC:
/etc/ssl/certs
Met behulp van het root-certificaat kunnen we controleren of we het volgende (tussenliggende) certificaat kunnen vertrouwen. Indien vertrouwd gebruiken we dit intermediaire certificaat en controleren we of het volgende certificaat te vertrouwen is. Dit doen we tot we bij het servercertificaat aankomen.
Het OpenSSL commando voor de www.badssl.com website:
openssl verify -x509_strict -CApath /etc/ssl/certs -untrusted 1.pem 0.pem
Als we het root-certificaat hebben, kunnen we dat doen:
openssl verify -x509_strict -no-CApath -CAfile root.pem -untrusted 1.pem 0.pem
De vlag 'untrusted' vertelt OpenSSL dat 1.pem niet te vertrouwen is en moet worden vertrouwd voordat 0.pem wordt gecontroleerd. Similary, we kunnen de keten controleren voor de two-intermediate-certs-example.org website die twee tussenliggende certificaten heeft:
openssl verify -x509_strict -no-CApath -CAfile root.pem -untrusted 2.pem -untrusted 1.pem 0.pem
Merk op dat als u de tussenliggende certificaten 1.pem en 2.pem omwisselt voor de website two-intermediate-certs-example.org , het resultaat in beide gevallen hetzelfde is. Dit betekent dat OpenSSL eerst de volgorde van de keten probeert te vinden en vervolgens de vertrouwensoperatie start. In dit geval kunnen we ook de twee tussenliggende certificaten samenvoegen tot 12.pem en laten lopen:
openssl verify -x509_strict -no-CApath -CAfile root.pem -untrusted 12.pem 0.pem
De SSL-certificaten die ik van de host heb gedownload staan in self.host_certs en zijn ook opgeslagen in bestanden 0.pem, 1.pem, ... Met behulp van Python's subprocess is de 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
Dit is lelijk en vereist dat we eerst PEM bestanden opslaan voordat we subprocess oproepen.
Sinds pyOpenSSL 20.0.0 kunnen we dit veel eleganter doen:
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
De verify_certificate() methode genereert een uitzondering is als de keten niet geverifieerd kan worden.
De vertrouwensketen, stap voor stap
Hierboven hebben we de Chain of Trust gecontroleerd met een enkel OpenSSL commando. Maar we kunnen dit ook op een andere manier doen. Beginnend met het vertrouwde root-certificaat op mijn PC proberen we eerst het laatste tussenliggende certificaat van de host te vertrouwen. Als dit voorbij is, gebruiken we het nu vertrouwde tussenliggende certificaat om te proberen het volgende untrusted certificaat te vertrouwen tot aan het servercertificaat. Met OpenSSL gebruiken we de vlag 'partial_chain'.
Voor de website two-intermediate-certs-example.org kunnen we bijvoorbeeld de volgende commando's geven:
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
Zelf ondertekende (tussen)certificaten
Wanneer een tussentijds certificaat zelf wordt ondertekend, stopt OpenSSL met valideren(?). Dit betekent dat we willen controleren of een certificaat dat van de host is gedownload zelf ondertekend is.
Een certificaat is zelf ondertekend wanneer:
- de Subject-CN en de Issuer-CN overeenkomen.
- de subjectKeyIdentifier en de authorityKeyIdentifier wedstrijd
- het certificaat bevat een sleutelgebruiksextensie met de KU_KEY_CERT_SIGN bitreeks
Ik heb alleen de eerste twee geïmplementeerd, geen idee op het moment dat ik het derde punt moet doen.
Tijdens het testen vond ik voor self-signed.badssl.com:
subjectKeyIdentifier: None
authorityKeyIdentifier: None
maar voor 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
Ik neem aan dat ik hier de leidende 'keyid:' moet verwijderen voordat ik de subjectKeyIdentifier vergelijk met de authorityKeyIdentifier.
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
Meer over zelf-ondertekende certificaten is te vinden in het artikel 'How to know if certificate is self-signed', zie onderstaande links.
Hier wordt ook verwezen naar RFC 3280:
Een certificaat wordt zelf uitgegeven als de DN's die in het onderwerp en het veld van de emittent staan, identiek zijn en niet leeg zijn. In het algemeen zijn de emittent en het onderwerp van de certificaten die een pad vormen voor elk certificaat verschillend. Een CA kan echter een certificaat aan zichzelf uitgeven ter ondersteuning van key rollover of wijzigingen in het certificaatbeleid. Deze zelf uitgegeven certificaten worden niet meegeteld bij de evaluatie van de padlengte of de naambeperkingen.
Het veld keyIdentifier van de uitbreiding authorityKeyIdentifier MOET worden opgenomen in alle certificaten die worden gegenereerd door conforme CA's om de constructie van het certificeringspad te vergemakkelijken. Er is één uitzondering; wanneer een CA zijn openbare sleutel verspreidt in de vorm van een "zelf ondertekend" certificaat, mag de sleutelidentificatiecode van de autoriteit worden weggelaten. De handtekening op een zelfondertekend certificaat wordt gegenereerd met de privésleutel die bij de openbare sleutel van het certificaat hoort. (Dit bewijst dat de emittent zowel de openbare als de particuliere sleutel bezit.) In dit geval zouden het onderwerp en de autoriteitssleutel identiek zijn, maar alleen het onderwerpssleutelsymbool is nodig voor de opbouw van het certificeringspad.
Verwarring met example.com, example.org
Tijdens de tests heb ik ook example.com en example.org gebruikt. De gastheer gaf drie certificaten terug. Ik controleerde het resultaat door certificaten te vervangen en te kijken wat er gebeurt.
openssl verify -x509_strict -no-CApath -CAfile root.pem -untrusted 2.pem -untrusted 1.pem 0.pem
Het was een puinhoop. Ik kon tussenliggende certificaten 2.pem en OpenSSL nog verwijderen: OK. Ik kon 1.pem vervangen door een ander willekeurig certificaat en OpenSSL zei nog: OK. Wat was hier aan de hand?
Toen ik naar de certificaten keek die de host terugstuurde, vond ik dat het derde certificaat, 2.pem, zelf ondertekend was en identiek was aan het basiscertificaat op mijn PC. Dit betekent dat deze domeinen mij een hoofdcertificaat sturen? OpenSSL klaagt niet, browsers klagen niet.
Ik heb dit niet verder onderzocht, maar ik dacht dit te vermelden om hoofdpijn te voorkomen ... :-(
De verkeerde host check
De verkeerde gastheercontrole moet ervoor zorgen dat het certificaat voor deze gastheer is. Ik zag niet hoe dit met pyOpenSSL kan worden gedaan, in feite zag ik geen manier om dit met OpenSSL te doen. OpenSSL waarschuwt u niet als het certificaat voor een andere host is.
We kunnen Wget gebruiken:
wget wrong.host.badssl.com
Resultaat:
ERROR: no certificate subject alternative name matches requested host name ‘wrong.host.badssl.com’.
Of we kunnen Curl gebruiken:
curl -L wrong.host.badssl.com
Resultaat:
curl: (51) SSL: no alternative certificate subject name matches target host name 'wrong.host.badssl.com'
In Python kunnen we dit doen met de Python requests library. Een verkeerde host geeft de uitzondering:
...
request.get(...)
...
Resultaat:
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'",),))
Merk op dat de uitzondering 'hostname ... doesn't match' alleen verschijnt als andere tests slagen. Als er ergens een probleem is met de certificaten krijgt u een Exception 'certificate verify failed'.
De klasse CertInfo()
Ik heb wat Python code geschreven om hiermee te spelen. Het belangrijkste is de CertInfo() klasse. Hier decodeer ik het certificaat.
# 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')
Niet alle eigenschappen van het certificaat zijn beschikbaar. Ik heb bijvoorbeeld geen gemakkelijke manier gevonden om de Signature te krijgen.
Om het certificaat af te drukken:
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)))
Samenvatting
Dit was een lange reis. Ik was op zoek naar een Python pyOpenSSL enige oplossing om de Chain of Trust te verifiëren zonder subprocess en kwam de hele tijd pagina's tegen die me vertelden dat dit niet mogelijk was met alleen pyOpenSSL . Hierna controleerde ik de pyOpenSSL repository op Github.com en er stond dat dit zeer recentelijk was toegevoegd. De changelog toonde de parameters en methoden en na het lezen van de documenten was het eenvoudig te implementeren.
Ik heb ook manieren gevonden om de certificaatketen te controleren, of een certificaat zelf ondertekend is, verlopen is en een manier om te controleren op een verkeerde host. Nu op naar het uitbreiden van mijn server check script ...
Links / credits
[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
Lees meer
Cryptography pyOpenSSL
Laat een reactie achter
Reageer anoniem of log in om commentaar te geven.
Opmerkingen (2)
Laat een antwoord achter
Antwoord anoniem of log in om te antwoorden.
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?
Recent
- Database UUID primaire sleutels van je webapplicatie verbergen
- Don't Repeat Yourself (DRY) met Jinja2
- SQLAlchemy, PostgreSQL, maximum aantal rijen per user
- Toon de waarden in SQLAlchemy dynamische filters
- Veilige gegevensoverdracht met Public Key versleuteling en pyNaCl
- rqlite: een alternatief voor SQLite met hoge beschikbaarheid en distributed
Meest bekeken
- Met behulp van Python's pyOpenSSL om SSL-certificaten die van een host zijn gedownload te controleren
- Gebruik van UUIDs in plaats van Integer Autoincrement Primary Keys met SQLAlchemy en MariaDb
- Maak verbinding met een dienst op een Docker host vanaf een Docker container
- PyInstaller en Cython gebruiken om een Python executable te maken
- SQLAlchemy: Gebruik van Cascade Deletes om verwante objecten te verwijderen
- Flask RESTful API verzoekparametervalidatie met Marshmallow-schema's