If you use the OpenSSL library in Python, you have get_peer_cert_chain that you can apply on your connection object, it will give you a list of certificates as sent by the server, so the end certificate with all intermediates one if needed.
See https://pyopenssl.org/en/stable/api/ssl.html#connection-objects :
get_peer_cert_chain()
Retrieve the other side’s certificate (if any)
Returns: A list of X509 instances giving the peer’s certificate chain, or None if it does not have one.
Here is one crude example (without any error handling):
from OpenSSL import SSL
import socket
dst = ('www.google.com', 443)
ctx = SSL.Context(SSL.SSLv23_METHOD)
s = socket.create_connection(dst)
s = SSL.Connection(ctx, s)
s.set_connect_state()
s.set_tlsext_host_name(dst[0])
s.sendall('HEAD / HTTP/1.0\n\n')
s.recv(16)
certs = s.get_peer_cert_chain()
for pos, cert in enumerate(certs):
print "Certificate #" + str(pos)
for component in cert.get_subject().get_components():
print "Subject %s: %s" % (component)
print "notBefore:" + cert.get_notBefore()
print "notAfter:" + cert.get_notAfter()
print "version:" + str(cert.get_version())
print "sigAlg:" + cert.get_signature_algorithm()
print "digest:" + cert.digest('sha256')
which gives:
Certificate #0
Subject C: US
Subject ST: California
Subject L: Mountain View
Subject O: Google LLC
Subject CN: www.google.com
notBefore:20180612133452Z
notAfter:20180821121300Z
version:2
sigAlg:sha256WithRSAEncryption
digest:06:C5:12:EB:3C:B1:7F:AB:18:E0:D5:22:E4:25:12:A7:30:AA:27:16:0B:3A:99:CB:3D:11:CF:12:EF:95:2E:41
Certificate #1
Subject C: US
Subject O: Google Trust Services
Subject CN: Google Internet Authority G3
notBefore:20170615000042Z
notAfter:20211215000042Z
version:2
sigAlg:sha256WithRSAEncryption
digest:BE:0C:CD:54:D4:CE:CD:A1:BD:5E:5D:9E:CC:85:A0:4C:2C:1F:93:A5:22:0D:77:FD:E8:8F:E9:AD:08:1F:64:1B
So you have the full detailed content of the certificate, see https://pyopenssl.org/en/stable/api/crypto.html#x509-objects for available info.
Then you have to_cryptography() to be able to get its PEM version with something like: cert.to_cryptography().public_bytes(serialization.Encoding.PEM)
But take into account also that:
- it would be better to use a callback, see the
set_info_callback() method, so that the diagnostics are run at the appropriate time (even type SSL.SSL_CB_HANDSHAKE_DONE)
- I have observed you need to exchange some traffic (send/recv) before being able to call
get_peer_cert_chain() (if you move it before sendall() in my example, it returns None)
From the link in your question, it seems you have the equivalent with getpeercertchain() when just using ssl and not OpenSSL; however this still seems to be recorded as a bug with some patches available and may not be released.
In fact the latest documentation at https://docs.python.org/3.8/library/ssl.html does not list getpeercertchain().