1#!/usr/bin/env python3 2# 3# fetch the certificate that the server(s) are providing in PEM form 4# 5# args are HOST:PORT [, HOST:PORT...] 6# 7# By Bill Janssen. 8 9import re 10import os 11import sys 12import tempfile 13 14 15def fetch_server_certificate (host, port): 16 17 def subproc(cmd): 18 from subprocess import Popen, PIPE, STDOUT, DEVNULL 19 proc = Popen(cmd, stdout=PIPE, stderr=STDOUT, stdin=DEVNULL) 20 status = proc.wait() 21 output = proc.stdout.read() 22 return status, output 23 24 def strip_to_x509_cert(certfile_contents, outfile=None): 25 m = re.search(br"^([-]+BEGIN CERTIFICATE[-]+[\r]*\n" 26 br".*[\r]*^[-]+END CERTIFICATE[-]+)$", 27 certfile_contents, re.MULTILINE | re.DOTALL) 28 if not m: 29 return None 30 else: 31 tn = tempfile.mktemp() 32 with open(tn, "wb") as fp: 33 fp.write(m.group(1) + b"\n") 34 try: 35 tn2 = (outfile or tempfile.mktemp()) 36 cmd = ['openssl', 'x509', '-in', tn, '-out', tn2] 37 status, output = subproc(cmd) 38 if status != 0: 39 raise RuntimeError('OpenSSL x509 failed with status %s and ' 40 'output: %r' % (status, output)) 41 with open(tn2, 'rb') as fp: 42 data = fp.read() 43 os.unlink(tn2) 44 return data 45 finally: 46 os.unlink(tn) 47 48 cmd = ['openssl', 's_client', '-connect', '%s:%s' % (host, port), '-showcerts'] 49 status, output = subproc(cmd) 50 51 if status != 0: 52 raise RuntimeError('OpenSSL connect failed with status %s and ' 53 'output: %r' % (status, output)) 54 certtext = strip_to_x509_cert(output) 55 if not certtext: 56 raise ValueError("Invalid response received from server at %s:%s" % 57 (host, port)) 58 return certtext 59 60 61if __name__ == "__main__": 62 if len(sys.argv) < 2: 63 sys.stderr.write( 64 "Usage: %s HOSTNAME:PORTNUMBER [, HOSTNAME:PORTNUMBER...]\n" % 65 sys.argv[0]) 66 sys.exit(1) 67 for arg in sys.argv[1:]: 68 host, port = arg.split(":") 69 sys.stdout.buffer.write(fetch_server_certificate(host, int(port))) 70 sys.exit(0) 71