0

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.

  • no it does not. I tried to put ssock.getpeercert(True), and the same error is shown. I can try to add ssl.get_server_certificate command and call it trough try..except . still hoping to just use ssock.getpeercert() command only – Sharuzzaman Ahmat Raslan Feb 16 '22 at 10:20
  • 2
    "But for cert that already expired, an error was raised during the socket handshake due to the cert expiry." By design. If you use an high level library, it does all validity check for you so it will abort the TLS handshake if there is a problem. In Python have a look at PyOpenSSL module instead of internal `ssl` one, it might give you better control of what is happening (see `set_verify()`) – Patrick Mevzek Feb 16 '22 at 10:24
  • 1
    Yes, SSL (TLS, really) socket creation is abstracted away and in all high-level libraries, and the design goal here is that the connection fails hard when something is fishy. You need to use a low-level library that can do half a handshake. – Tomalak Feb 16 '22 at 10:28
  • Inserting `ssl.get_server_certificate()` was the entire point of my duplicate link. It's clear that the other things don't work. – Tomalak Feb 16 '22 at 10:53
  • `create_default_context` creates a context **with** certificate validation and that's why it fails. See [Python getting common name from URL using ssl.getpeercert()](https://stackoverflow.com/questions/45478536/python-getting-common-name-from-url-using-ssl-getpeercert) on how to reach your goal. – Steffen Ullrich Feb 16 '22 at 12:51
  • @SteffenUllrich I will review that post and figure out a solution – Sharuzzaman Ahmat Raslan Feb 16 '22 at 13:18

1 Answers1

0

I managed so 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()