get-remote-certificate.py 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  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. import re
  9. import os
  10. import sys
  11. import tempfile
  12. def fetch_server_certificate (host, port):
  13. def subproc(cmd):
  14. from subprocess import Popen, PIPE, STDOUT
  15. proc = Popen(cmd, stdout=PIPE, stderr=STDOUT, shell=True)
  16. status = proc.wait()
  17. output = proc.stdout.read()
  18. return status, output
  19. def strip_to_x509_cert(certfile_contents, outfile=None):
  20. m = re.search(br"^([-]+BEGIN CERTIFICATE[-]+[\r]*\n"
  21. br".*[\r]*^[-]+END CERTIFICATE[-]+)$",
  22. certfile_contents, re.MULTILINE | re.DOTALL)
  23. if not m:
  24. return None
  25. else:
  26. tn = tempfile.mktemp()
  27. fp = open(tn, "wb")
  28. fp.write(m.group(1) + b"\n")
  29. fp.close()
  30. try:
  31. tn2 = (outfile or tempfile.mktemp())
  32. status, output = subproc(r'openssl x509 -in "%s" -out "%s"' %
  33. (tn, tn2))
  34. if status != 0:
  35. raise RuntimeError('OpenSSL x509 failed with status %s and '
  36. 'output: %r' % (status, output))
  37. fp = open(tn2, 'rb')
  38. data = fp.read()
  39. fp.close()
  40. os.unlink(tn2)
  41. return data
  42. finally:
  43. os.unlink(tn)
  44. if sys.platform.startswith("win"):
  45. tfile = tempfile.mktemp()
  46. fp = open(tfile, "w")
  47. fp.write("quit\n")
  48. fp.close()
  49. try:
  50. status, output = subproc(
  51. 'openssl s_client -connect "%s:%s" -showcerts < "%s"' %
  52. (host, port, tfile))
  53. finally:
  54. os.unlink(tfile)
  55. else:
  56. status, output = subproc(
  57. 'openssl s_client -connect "%s:%s" -showcerts < /dev/null' %
  58. (host, port))
  59. if status != 0:
  60. raise RuntimeError('OpenSSL connect failed with status %s and '
  61. 'output: %r' % (status, output))
  62. certtext = strip_to_x509_cert(output)
  63. if not certtext:
  64. raise ValueError("Invalid response received from server at %s:%s" %
  65. (host, port))
  66. return certtext
  67. if __name__ == "__main__":
  68. if len(sys.argv) < 2:
  69. sys.stderr.write(
  70. "Usage: %s HOSTNAME:PORTNUMBER [, HOSTNAME:PORTNUMBER...]\n" %
  71. sys.argv[0])
  72. sys.exit(1)
  73. for arg in sys.argv[1:]:
  74. host, port = arg.split(":")
  75. sys.stdout.buffer.write(fetch_server_certificate(host, int(port)))
  76. sys.exit(0)