pythonpython-3.xssl

Python: how to get expired SSL cert date?


I created a simple Python program to get the expiry date of SSL cert, from reference on the Internet. It works correctly for cert that is still not expired. But for cert that already expired, an error was raised during the socket handshake due to the cert expiry.

How do I get the expired cert info to extract the expiry date because the connection is refused. Is there a way to force the socket connection to establish even though the cert might be expired?

Code:

import ssl
from cryptography import x509
import sys
import socket

hostname = sys.argv[1]

context = ssl.create_default_context()

with socket.create_connection((hostname, 443)) as sock:
    with context.wrap_socket(sock, server_hostname=hostname) as ssock:
        print("SSL/TLS version:",ssock.version())
        print()
        data = ssock.getpeercert()
        print("Data:",data)
        print()
        notafter_date = data["notAfter"]
        print("Expiry date:",notafter_date)
        print()

Output for not expired cert:

$ python check_ssl_cert.py badssl.com
SSL/TLS version: TLSv1.2

Data: {'subject': ((('countryName', 'US'),), (('stateOrProvinceName', 'California'),), (('localityName', 'Walnut Creek'),), (('organizationName', 'Lucas Garron Torres'),), (('commonName', '*.badssl.com'),)), 'issuer': ((('countryName', 'US'),), (('organizationName', 'DigiCert Inc'),), (('commonName', 'DigiCert SHA2 Secure Server CA'),)), 'version': 3, 'serialNumber': '0AF06CDA37A60B641342F0A1EB1D59FD', 'notBefore': 'Mar 23 00:00:00 2020 GMT', 'notAfter': 'May 17 12:00:00 2022 GMT', 'subjectAltName': (('DNS', '*.badssl.com'), ('DNS', 'badssl.com')), 'OCSP': ('http://ocsp.digicert.com',), 'caIssuers': ('http://cacerts.digicert.com/DigiCertSHA2SecureServerCA.crt',), 'crlDistributionPoints': ('http://crl3.digicert.com/ssca-sha2-g6.crl', 'http://crl4.digicert.com/ssca-sha2-g6.crl')}

Expiry date: May 17 12:00:00 2022 GMT

Output for expired cert:

$ python check_ssl_cert.py expired.badssl.com
Traceback (most recent call last):
  File "check_ssl_cert.py", line 11, in <module>
    with context.wrap_socket(sock, server_hostname=hostname) as ssock:
  File "/usr/lib/python3.7/ssl.py", line 423, in wrap_socket
    session=session
  File "/usr/lib/python3.7/ssl.py", line 870, in _create
    self.do_handshake()
  File "/usr/lib/python3.7/ssl.py", line 1139, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:1091)

As suggested for the other solution, it does not solve the issue.

I tried changing the line

data = ssock.getpeercert()

to

data = ssock.getpeercert(True)

and a DER formatted cert is returned for not expired cert, but got cert verification error for already expired cert.


Solution

  • I managed to create a working solution. Check my Github gist here: https://gist.github.com/sharuzzaman/8827ef0d9fff89e4e937579b2b01653f

    Also the verbatim code here for quick reference

    #!/bin/env python3
    
    # check_ssl_cert.py - python get info for expired SSL cert
    # Copyright 2022 Sharuzzaman Ahmat Raslan <sharuzzaman@gmail.com>
    
    # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
    # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
    # You should have received a copy of the GNU Lesser General Public License along with this program. If not, see https://www.gnu.org/licenses/.
    
    from cryptography import x509
    import socket
    import ssl
    import sys
    
    hostname = sys.argv[1]
    
    # create default context
    context = ssl.create_default_context()
    
    # override context so that it can get expired cert
    context.check_hostname = False
    context.verify_mode = ssl.CERT_NONE
    
    with socket.create_connection((hostname, 443)) as sock:
        with context.wrap_socket(sock, server_hostname=hostname) as ssock:
            print("SSL/TLS version:", ssock.version())
            print()
    
            # get cert in DER format
            data = ssock.getpeercert(True)
            print("Data:", data)
            print()
    
            # convert cert to PEM format
            pem_data = ssl.DER_cert_to_PEM_cert(data)
            print("PEM cert:", pem_data)
    
            # pem_data in string. convert to bytes using str.encode()
            # extract cert info from PEM format
            cert_data = x509.load_pem_x509_certificate(str.encode(pem_data))
    
            # show cert expiry date
            print("Expiry date:", cert_data.not_valid_after)
            print()