My goal is to retrieve the TLS certificates from online services and calculate the amount of seconds until they expire using Python. I have solved this for HTTPS:
import socket
import ssl
from datetime import datetime
from pytz as pytz
from asn1crypto.x509 import Certificate
def https_ttl(host, port):
context = ssl.create_deafult_context()
# we just want to check the certificate expiry date, we do not need to validate the chain of trust
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
with socket.create_connection((host, port)) as tcp_socket:
with context.wrap_socket(tcp_socket, server_hostname=host) as ssl_socket:
# getpeercert(binary_form=False) returns empty dict if verification is disabled
cert = Certificate.load(ssl_socket.getpeercert(binary_form=True))
return (cert.not_valid_after - datetime.now(pytz.utc)).total_seconds()
When I use https_ttl() to retrieve the certificate from an OpenLDAP server configured for STARTTLS, I receive this error message:
my_code.py:21: in https_ttl
with self._context.wrap_socket(tcp_socket, server_hostname=host) as ssl_socket:
../../.pyenv/versions/3.8.12/lib/python3.8/ssl.py:500: in wrap_socket
return self.sslsocket_class._create(
../../.pyenv/versions/3.8.12/lib/python3.8/ssl.py:1040: in _create
self.do_handshake()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <ssl.SSLSocket [closed] fd=-1, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0>
block = False
@_sslcopydoc
def do_handshake(self, block=False):
self._check_connected()
timeout = self.gettimeout()
try:
if timeout == 0.0 and block:
self.settimeout(None)
> self._sslobj.do_handshake()
E ssl.SSLEOFError: EOF occurred in violation of protocol (_ssl.c:1131)
../../.pyenv/versions/3.8.12/lib/python3.8/ssl.py:1309: SSLEOFError
However, I can retrieve the certificate using openssl directly:
$ echo | openssl s_client -connect localhost:8389 -starttls ldap 2> /dev/null | sed -n '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p'
-----BEGIN CERTIFICATE-----
MIIXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXBgNV
BAMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXMDAx
WhcXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXZW1h
bHlXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX9hHH
HR2XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX3VYt
yyPXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX1ogL
8UzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXunIo
SGVXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXQO/u
PwCXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXdt9F
qcXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX8XKs
wHiXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXQIsu
AtEXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXz0U
ct8XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXJ3zc
ddcXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXYWM9
UB7XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXFJ+b
XCRXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXK7yw
ZrUXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXqJi3
L6fXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX133L
WQLXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXkMas
YuWXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXcH3a
7rdXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXDt0B
bdDXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXydm7
f6FXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXvgXA
PNzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXTiN/
+J7XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXsbC
BNGXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXqEjB
2x5XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXPwVF
ffBXXXXXXXXXXXXXXXXXXXXXX3pm
-----END CERTIFICATE-----
How can I replicate the openssl invocation using Python's ssl library?
Here is my solution based on @user207421's comment: