Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PyOpenSSL - how can I get SAN(Subject Alternative Names) list

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?

like image 516
Elon Salfati Avatar asked Mar 26 '18 12:03

Elon Salfati


2 Answers

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
like image 93
Anatolii Chmykhalo Avatar answered Oct 06 '22 00:10

Anatolii Chmykhalo


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:

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'])
like image 34
Ben Avatar answered Oct 05 '22 23:10

Ben