How can I decode a SSL certificate using python?
Asked Answered
S

9

44

How can I decode a pem-encoded (base64) certificate with Python? For example this here from github.com:

-----BEGIN CERTIFICATE-----
MIIHKjCCBhKgAwIBAgIQDnd2il0H8OV5WcoqnVCCtTANBgkqhkiG9w0BAQUFADBp
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSgwJgYDVQQDEx9EaWdpQ2VydCBIaWdoIEFzc3VyYW5j
ZSBFViBDQS0xMB4XDTExMDUyNzAwMDAwMFoXDTEzMDcyOTEyMDAwMFowgcoxHTAb
BgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYBBAGCNzwCAQMTAlVT
MRswGQYLKwYBBAGCNzwCAQITCkNhbGlmb3JuaWExETAPBgNVBAUTCEMzMjY4MTAy
MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2Fu
IEZyYW5jaXNjbzEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRMwEQYDVQQDEwpnaXRo
dWIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7dOJw11wcgnz
M08acnTZtlqVULtoYZ/3+x8Z4doEMa8VfBp/+XOvHeVDK1YJAEVpSujEW9/Cd1JR
GVvRK9k5ZTagMhkcQXP7MrI9n5jsglsLN2Q5LLcQg3LN8OokS/rZlC7DhRU5qTr2
iNr0J4mmlU+EojdOfCV4OsmDbQIXlXh9R6hVg+4TyBkaszzxX/47AuGF+xFmqwld
n0xD8MckXilyKM7UdWhPJHIprjko/N+NT02Dc3QMbxGbp91i3v/i6xfm/wy/wC0x
O9ZZovLdh0pIe20zERRNNJ8yOPbIGZ3xtj3FRu9RC4rGM+1IYcQdFxu9fLZn6TnP
pVKACvTqzQIDAQABo4IDajCCA2YwHwYDVR0jBBgwFoAUTFjLJfBBT1L0KMiBQ5um
qKDmkuUwHQYDVR0OBBYEFIfRjxlu5IdvU4x3kQdQ36O/VUcgMCUGA1UdEQQeMByC
CmdpdGh1Yi5jb22CDnd3dy5naXRodWIuY29tMIGBBggrBgEFBQcBAQR1MHMwJAYI
KwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBLBggrBgEFBQcwAoY/
aHR0cDovL3d3dy5kaWdpY2VydC5jb20vQ0FDZXJ0cy9EaWdpQ2VydEhpZ2hBc3N1
cmFuY2VFVkNBLTEuY3J0MAwGA1UdEwEB/wQCMAAwYQYDVR0fBFowWDAqoCigJoYk
aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL2V2MjAwOWEuY3JsMCqgKKAmhiRodHRw
Oi8vY3JsNC5kaWdpY2VydC5jb20vZXYyMDA5YS5jcmwwggHEBgNVHSAEggG7MIIB
tzCCAbMGCWCGSAGG/WwCATCCAaQwOgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cuZGln
aWNlcnQuY29tL3NzbC1jcHMtcmVwb3NpdG9yeS5odG0wggFkBggrBgEFBQcCAjCC
AVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0AGgAaQBzACAAQwBlAHIAdABp
AGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1AHQAZQBzACAAYQBjAGMAZQBw
AHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABpAGcAaQBDAGUAcgB0ACAAQwBQ
AC8AQwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBlAGwAeQBpAG4AZwAgAFAAYQBy
AHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBoAGkAYwBoACAAbABpAG0AaQB0
ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAgAGEAcgBlACAAaQBuAGMAbwBy
AHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAgAGIAeQAgAHIAZQBmAGUAcgBl
AG4AYwBlAC4wHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBEGCWCGSAGG
+EIBAQQEAwIGwDAOBgNVHQ8BAf8EBAMCBaAwDQYJKoZIhvcNAQEFBQADggEBABRS
cR+GnW01Poa7ZhqLhZi5AEzLQrVG/AbnRDnI6FLYERQjs3KW6RSUni8AKPfVBEVA
AMb0V0JC3gmJlxENFFxrvQv3GKNfZwLzCThjv8ESnTC6jqVUdFlTZ6EbUFsm2v0T
flkXv0nvlH5FpP06STLwav+JjalhqaqblkbIHOAYHOb7gvQKq1KmyuhUItnbKj1a
InuA6gcF1PnH8FNZX7t3ft6TcEFOI8t4eXnELurXZioY99HFfOISeIKNHeyCngGi
5QK+eKG5WVjFTG9PpTG0SVtemB4uOPYZxDmiSvt5BbjyWeUmEnCtwOh1Ix8Y0Qvg
n2Xkw9dJh1tybLEvrG8=
-----END CERTIFICATE-----

According to ssl-shopper it should be something like this:

Common Name: github.com
Subject Alternative Names: github.com, www.github.com
Organization: GitHub, Inc.
Locality: San Francisco
State: California
Country: US
Valid From: May 26, 2011
Valid To: July 29, 2013

How can I get this plaintext using python?

Selfridge answered 3/6, 2013 at 14:33 Comment(3)
DO you have to use python? The openssl cmdline output will do this for you.Tantra
I want to get some informaiton from the certifiate file in a python program ,on the fly.Selfridge
you could use asn1crypto, pyOpenSSL, M2Crypto, cryptography, pyasn1 python packages. Here's code example (in Russian)Cricoid
H
66

Python's standard library, even in the latest version, does not include anything that can decode X.509 certificates. However, the add-on cryptography package does support this. Quoting an example from the documentation:

>>> from cryptography import x509
>>> from cryptography.hazmat.backends import default_backend
>>> cert = x509.load_pem_x509_certificate(pem_data, default_backend())
>>> cert.serial_number
2

Another add-on package that might be an option is pyopenssl. This is a thin wrapper around the OpenSSL C API, which means it will be possible to do what you want, but expect to spend a couple days tearing your hair out at the documentation.

If you can't install Python add-on packages, but you do have the openssl command-line utility,

import subprocess
cert_txt = subprocess.check_output(["openssl", "x509", "-text", "-noout", 
                                    "-in", certificate])

should produce roughly the same stuff you got from your web utility in cert_txt.

Incidentally, the reason doing a straight-up base64 decode gives you binary gobbledygook is that there are two layers of encoding here. X.509 certificates are ASN.1 data structures, serialized to X.690 DER format and then, since DER is a binary format, base64-armored for ease of file transfer. (A lot of the standards in this area were written way back in the nineties when you couldn’t reliably ship anything but seven-bit ASCII around.)

Horan answered 3/6, 2013 at 14:53 Comment(4)
pem_data in the example above needs to be a bytes object, not a str object.Lund
My cert was a string : pem_data = ssl.get_server_certificate((hostname, port)) so I had to convert that to byte in : cert = x509.load_pem_x509_certificate(str.encode(pem_data), default_backend())Corruptible
Wanted to update the function signature on load_pem_x509_certificate: per the docs, it is now: cryptography.x509.load_pem_x509_certificate(data). You don't need the default_backend.Coryphaeus
To have a full example of powerful copy-paste, missing print ('{0:x}'.format(int(cert.serial_number)) )Watson
L
32

You can use pyasn1 and pyasn1-modules packages to parse this kind of data. For instance:

from pyasn1_modules import pem, rfc2459
from pyasn1.codec.der import decoder

substrate = pem.readPemFromFile(open('cert.pem'))
cert = decoder.decode(substrate, asn1Spec=rfc2459.Certificate())[0]
print(cert.prettyPrint())

Read the docs for pyasn1 for the rest.

Luckless answered 16/7, 2013 at 6:40 Comment(0)
A
28

Notes:

Regarding the certificate (PEM) from the question:

  • Saved it in a file called q016899247.crt (in the script (code00.py) dir)

  • The end tag: ("-----END CERTIFICATE----") was missing a hyphen (-) at the end; corrected in Question @VERSION #4.)

code00.py:

#!/usr/bin/env python

import os
import ssl
import sys
from pprint import pprint as pp


def main(*argv):
    cert_file_base_name = "q016899247.crt"
    cert_file_name = os.path.join(os.path.dirname(__file__), cert_file_base_name)
    try:
        cert_dict = ssl._ssl._test_decode_cert(cert_file_name)
    except Exception as e:
        print("Error decoding certificate: {:}".format(e))
    else:
        print("Certificate ({:s}) data:\n".format(cert_file_base_name))
        pp(cert_dict)


if __name__ == "__main__":
    print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                   64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    rc = main(*sys.argv[1:])
    print("\nDone.")
    sys.exit(rc)

Output:

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q016899247]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" ./code00.py
Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] 064bit on win32

Certificate (q016899247.crt) data:

{'OCSP': ('http://ocsp.digicert.com',),
 'caIssuers': ('http://www.digicert.com/CACerts/DigiCertHighAssuranceEVCA-1.crt',),
 'crlDistributionPoints': ('http://crl3.digicert.com/ev2009a.crl',
                           'http://crl4.digicert.com/ev2009a.crl'),
 'issuer': ((('countryName', 'US'),),
            (('organizationName', 'DigiCert Inc'),),
            (('organizationalUnitName', 'www.digicert.com'),),
            (('commonName', 'DigiCert High Assurance EV CA-1'),)),
 'notAfter': 'Jul 29 12:00:00 2013 GMT',
 'notBefore': 'May 27 00:00:00 2011 GMT',
 'serialNumber': '0E77768A5D07F0E57959CA2A9D5082B5',
 'subject': ((('businessCategory', 'Private Organization'),),
             (('jurisdictionCountryName', 'US'),),
             (('jurisdictionStateOrProvinceName', 'California'),),
             (('serialNumber', 'C3268102'),),
             (('countryName', 'US'),),
             (('stateOrProvinceName', 'California'),),
             (('localityName', 'San Francisco'),),
             (('organizationName', 'GitHub, Inc.'),),
             (('commonName', 'github.com'),)),
 'subjectAltName': (('DNS', 'github.com'), ('DNS', 'www.github.com')),
 'version': 3}

Done.
Aeolian answered 28/4, 2018 at 3:9 Comment(5)
calling underscore prefixed functions is generally a no-no, they're intended for internal use only, and there aren't the same guarantees that the function signature will stay the same from version to same version. That said, good work!Hollinger
@Twirrim: Thx, I knew I forgot smth when I last edited the post. I rephrased it so it better reflects reality.Aeolian
Good way to do decode SSL cert into text in env where cryptography modules are not allowed. Though not sure about calling internal SSL functions.Halhalafian
@AbySamRoss: ssl is a cryptography module (it's just part of Python standard library). And it delegates the actual work to OpenSSL (or other such cryptography provider). Which all the other modules do.Aeolian
I know it's prohibited fruit, but that's so awesome. Thanks to you, I was able to write a Python library/script that's able to download a whole certificate chain, as long as caIssuers extension is configured on the certificates. gist.github.com/flisboac/48762e176061b520a606868bc4ce089f. Thank you so much for this idea!Garretson
C
6

This allows to extract certain values from SSL certificate:

from cryptography import x509
from cryptography.hazmat.backends import default_backend

hostname = 'google.com.com'
port = 443
cert = ssl.get_server_certificate((hostname, port))
certDecoded = x509.load_pem_x509_certificate(str.encode(cert), 
default_backend())
print(certDecoded.issuer)
print(certDecoded.subject)
print(certDecoded.not_valid_after)
print(certDecoded.not_valid_before)
Corruptible answered 15/1, 2021 at 17:39 Comment(1)
This has worked for me. If you use it for local files, open it as binary: with open(cert_filepath, 'br') as cert_content: cert_data = cert_content.read() cert_decoded = x509.load_pem_x509_certificate(cert_data, default_backend())Foofaraw
V
5

This code dumps a cert file content:

import OpenSSL.crypto

cert = OpenSSL.crypto.load_certificate(
      OpenSSL.crypto.FILETYPE_PEM,
      open('/path/to/cert/file.crt').read()
)

print OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_TEXT, cert)

Give it a go.

Verada answered 26/7, 2018 at 8:10 Comment(0)
M
3

There's another way to use _test_decode_certificate without using the internal implementation. It's a bit hacky in a different way, though

import ssl

ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)

# The filepath to your PEM-encoded x509 cert
ctx.load_verify_locations("369fa1ef21f5476c02814c637d83f71d851f867348eef21d1eb0058671d0e5a6.crt")

certificate_details = ctx.get_ca_certs()

Under the hood, this is another entrypoint to the _decode_certificate function which _test_decode_certificate uses.

You can see how this works in the CPython source code https://github.com/python/cpython/blob/main/Modules/_ssl.c#L4578

Maryannmaryanna answered 20/6, 2021 at 23:32 Comment(1)
Best answer IMO if you can't install any third-party libs. Small nit: ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) to avoid deprecation warnings.Lethargy
A
2

You can download the code from here. It is purely extracting data from a .pem & .cer type certificates.

Else, pem certificate can be decoded using the following snippet:

    #import pem & pyOpenSSL module

    certs = pem.parse_file(file_path)  # using pem module
    for pem_certificates in certs:
        strcert = str(pem_certificates)
        loadCert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,strcert)
        print(loadCert.get_issuer())```

 
Aran answered 14/1, 2021 at 11:36 Comment(0)
H
0
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
import os

with open("es01.pem", "rb") as f:
    cert= x509.load_pem_x509_certificate(f.read(), default_backend())

print (f"Serial Number: {cert.serial_number}")
print (f"Extensions: {cert.extensions}")

fingerprint_hashed_byte_string = cert.fingerprint(hashes.SHA256())
fingerprint_hex_string = ':'.join([format(byte, '02x') for byte in fingerprint_hashed_byte_string])
print (f"Fingerprint: {fingerprint_hex_string}")
print (f"Issuer: {cert.issuer}")
print (f"Not Valid Before: {cert.not_valid_before}")
print (f"Not Valid After: {cert.not_valid_after}")

"""
OR SIMPLY USING OPENSSL...

openssl x509 -in es01.pem -noout -serial
serial=12D498EEF25477293EE0CB9A287E530FE2466EF2

openssl x509 -noout -fingerprint -sha256 -inform pem -in es01.pem
sha256 Fingerprint=69:B5:89:3E:7C:9F:D8:70:B1:B5:55:97:B3:87:43:CB:3F:55:D1:0E:8E:D6:58:9B:19:7E:CA:F6:B3:F5:17:50

openssl x509 -noout -ext basicConstraints -inform pem -in es01.pem
X509v3 Basic Constraints: 
    CA:FALSE

openssl x509 -in es01.pem -noout -ext subjectAltName
X509v3 Subject Alternative Name: 
    DNS:es01, IP Address:127.0.0.1, DNS:localhost

openssl x509 -in es01.pem -noout -startdate
notBefore=Apr 18 15:36:36 2023 GMT

openssl x509 -in es01.pem -noout -enddate
notAfter=Apr 17 15:36:36 2025 GMT

openssl x509 -in es01.pem -noout -issuer
issuer=C = US, ST = Minneosta, L = Bloomington, O = TEST-CA, OU = CERTS, CN = INTERMEDIATE-CA
"""
Hebetate answered 7/6, 2023 at 20:25 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Cowbane
G
-3

I am not Sure how you received it but another simple way for getting it installed is to just write it as a binary file and then run it using os

import os

cert= function_gives_binary_cert()
with open('RecvdCert.der','wb') as file:
     file.write(cert)

os.startfile('RecvdCert.der')

Beware of running received binary from an unknown source. Just want to decode then use OpenSSL as mentioned in other answers.

Gallegos answered 8/7, 2019 at 4:52 Comment(1)
os.startfile is only available on Windows and it does not execute the file, it opens it with an application instead. Also, the certificate is not an executable. The result of os.startfile depends on which application has been registered to open the file extension. It is similar to double clicking the file on Windows shell.Pigg

© 2022 - 2024 — McMap. All rights reserved.