Is it possible to get the whole certificate chain in a PEM format using ssl with Python ? I can get the specific one with :
import ssl
addr = '192.0.2.1'
cert_str = ssl.get_server_certificate((addr, 443))
which give me something like :
-----BEGIN CERTIFICATE-----
[...]
-----END CERTIFICATE-----
But I'd like to have :
-----BEGIN CERTIFICATE-----
[...]
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
[...]
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
[...]
-----END CERTIFICATE-----
I'm pretty sure this is possible since I can download this from my web browser. Any idea ?
(I have already checked Getting certificate chain with Python 3.3 SSL module but I'm not sure this is what I want ...)
EDIT: What I tried after Patrick Mevzek answer :
from OpenSSL import SSL
import socket
dst = ('192.0.2.1', 443)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ctx = SSL.Context(SSL.SSLv23_METHOD)
ctx.set_alpn_protos([b'http/1.1'])
if sock.connect_ex(dst) == 0:
connection = SSL.Connection(ctx, sock)
cert_str = connection.get_peer_cert_chain()
But cert_str is None. I think this is because i'm missing something with the use of OpenSSL.
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:
set_info_callback()
method, so that the diagnostics are run at the appropriate time (even type SSL.SSL_CB_HANDSHAKE_DONE
)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()
.