简体   繁体   中英

Get a server certificate despite handshake failure in Python

I am writing a tool to monitor server certificate expiration. I'm using python3 ssl and socket modules to get the server cert using a pretty basic method of creating a default context, disabling hostname validation and certificate verification, calling SSLSocket.connect() , then SSLSocket.getpeercert() , with the sole purpose of grabbing the server certificate, and that is all.

This is all within a private network and I am not concerned with validation.

I have some devices that require client certs signed by a private CA (which my tool doesn't have), so the handshake fails on SSLSocket.connect() , making SSLSocket.getpeercert() impossible.

I know that the server certificate is indeed being provided to my client (along with that pesky Certificate Request) during the handshake. I can see it in a packet capture, as well as just using the openssl s_client command line.

Here is my code.

def get_cert(self, host, port):
    ctx = ssl.create_default_context()
    ctx.check_hostname = False
    ctx.verify_mode = ssl.CERT_NONE
    with ctx.wrap_socket(socket.socket(), server_hostname=host) as s:
        s.settimeout(10)
        s.connect((host, port))
        binary_cert = s.getpeercert(True)
        cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, binary_cert)
        pem_cert = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert).decode()
        return pem_cert

Is there any way to get a little lower into the handshake messages to get the server cert, even though the handshake ultimately fails?

My current solution is to just run openssl s_client -connect host:port using subprocess.run() in the event of a ssl.SSLError.

You may catch exception that do_handshake() produced and then continue to process server certificate.

import OpenSSL
import socket

dst = ('10.10.10.10', 443)
sock = socket.create_connection(dst)
context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
connection = OpenSSL.SSL.Connection(context, sock)
connection.set_connect_state()
try:
  connection.do_handshake()
except:
  print(connection.get_peer_cert_chain())

Tested on python 2.7.17 and 3.8.5

It looks like there's unfortunately no way to do it with python's ssl module in versions < 3.10. In those versions, the only way to get the peer certificate that I can see is through the low-level _ssl.SSLSocket.getpeercert() method and that immediately throws exception if the handshake is not complete.

Since python 3.10, there's a new _ssl.SSLSocket.get_unverified_chain() method that does not do the handshake check, so perhaps something like this abomination could work?

ssock = context.wrap_socket(sock, do_handshake_on_connect=False)

try:
    ssock.do_handshake()
except ssl.SSLError as e:
    pass

certs = ssock._sslobj._sslobj.get_unverified_chain()

... but I have not tested it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM