I'm trying to find a way to get the list of SAN from a given certificate, however, I couldn't find anything in the pyOpenSSL documentation.
Any Ideas on how can I pull this data from the certificate?
I'm trying to find a way to get the list of SAN from a given certificate, however, I couldn't find anything in the pyOpenSSL documentation.
Any Ideas on how can I pull this data from the certificate?
I found a way where we first check extension by name, and then, when "SAN" data found we get str
representation and return.
def get_certificate_san(x509cert):
san = ''
ext_count = x509cert.get_extension_count()
for i in range(0, ext_count):
ext = x509cert.get_extension(i)
if 'subjectAltName' in str(ext.get_short_name()):
san = ext.__str__()
return san
'DNS:altname1.net, DNS:altname2.net,, DNS:DNS:wtf'
for example –
Sarabia PyOpenSSL recommends using cryptography
as it provides a safer and better API. If you can install cryptography (it's a dependency of the requests
library, so many projects already have it installed), here's how you get the SAN:
from cryptography import x509
# classes must be subtype of:
# https://cryptography.io/en/latest/x509/reference/#cryptography.x509.ExtensionType
san = loaded_cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
print(san)
Here's a full example of retrieving a cert from a host and printing its common name and SAN.
import ssl
from cryptography import x509
from cryptography.hazmat.backends import default_backend
certificate: bytes = ssl.get_server_certificate(('example.com', 443)).encode('utf-8')
loaded_cert = x509.load_pem_x509_certificate(certificate, default_backend())
common_name = loaded_cert.subject.get_attributes_for_oid(x509.oid.NameOID.COMMON_NAME)
print(common_name)
# classes must be subtype of:
# https://cryptography.io/en/latest/x509/reference/#cryptography.x509.ExtensionType
san = loaded_cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
san_dns_names = san.value.get_values_for_type(x509.DNSName)
print(san_dns_names)
Alternatively, if you're downloading a cert from a host, Python's built-in ssl
library will parse the SANs for you (code from here):
from collections import defaultdict
import socket
import ssl
hostname = 'www.python.org'
context = ssl.create_default_context()
with socket.create_connection((hostname, 443)) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
# https://docs.python.org/3/library/ssl.html#ssl.SSLSocket.getpeercert
cert = ssock.getpeercert()
subject = dict(item[0] for item in cert['subject'])
print(subject['commonName'])
subjectAltName = defaultdict(set)
for type_, san in cert['subjectAltName']:
subjectAltName[type_].add(san)
print(subjectAltName['DNS'])
print(common_name[0].value)
–
Hufford Based on @anatolii-chmykhalo answer
This returns the altnames for DNS, based on the string representation.
def get_dns_altnames(req: OpenSSL.crypto.X509Req):
"""
Get DNS altnames from a X509Req certificate
"""
extensions = (ext for ext in req.get_extensions()
if ext.get_short_name() == b'subjectAltName')
dns_names = []
for ext in extensions:
for alt in str(ext).split(', '):
if alt.startswith('DNS:'):
dns_names.append(alt[4:])
return dns_names
I did some digging into it and I finally found something so if someone else will ever need the answer:
import OpenSSL
def extract_san_from_cert(cert_body):
'''
This function will extract the SAN (Subject Alt Names)
from the certificate
'''
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_body)
try:
crt = cert.get_extension(6)
data = crt.get_data()
# ignore first 4 bytes and split in \x82\18 (,)
san = data[4:].split('\x82\x18')
except IndexError as err:
# No SAN in the certificate
san = []
return san
\x18
is the length of your DNS name (and I'm guessing that \x82
is the type) –
Sarabia © 2022 - 2024 — McMap. All rights reserved.
ext.get_short_name()
is of type byte. I got that function working with the following hacky adjustment:if 'subjectAltName' in str(ext.get_short_name())
– Rimskykorsakov