get_cert.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. #!/usr/bin/env python3
  2. import hashlib
  3. import logging
  4. import ssl
  5. import socket
  6. import time
  7. import click
  8. logging.basicConfig(level=logging.INFO,
  9. format='[%(levelname)-4s] %(message)s',
  10. datefmt='%Y-%m-%d %H:%M')
  11. logger = logging.getLogger('certo')
  12. # The following inspired by:
  13. # https://stackoverflow.com/questions/17667903/python-socket-receive-large-amount-of-data
  14. # https://www.binarytides.com/receive-full-data-with-the-recv-socket-function-in-python/
  15. def recv_msg(sock, timeout):
  16. # Read message length and unpack it into an integer
  17. data = b''
  18. begin = time.time()
  19. n = 1024
  20. while len(data) < n or time.time() - begin < timeout:
  21. try:
  22. data += sock.recv(n - len(data))
  23. logger.debug("Partial: %r" % data)
  24. except socket.timeout:
  25. pass
  26. if b'Ready to start TLS\r\n' in data:
  27. break
  28. time.sleep(0.1)
  29. if len(data) == 0:
  30. return None
  31. logger.debug("Data: %r" % data)
  32. return data
  33. def establish_conn(addr, port, starttls):
  34. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  35. sock.settimeout(5)
  36. try:
  37. if starttls:
  38. logger.debug("Using STARTTLS")
  39. logger.debug("Connecting to %s:%s" % (addr, port))
  40. sock.connect((addr, port))
  41. sock.send(b"STARTTLS\r\n")
  42. data = recv_msg(sock, 5)
  43. if data is None:
  44. raise socket.error
  45. wrapped_socket = ssl.wrap_socket(sock)
  46. else:
  47. wrapped_socket = ssl.wrap_socket(sock)
  48. wrapped_socket.connect((addr, port))
  49. return wrapped_socket.getpeercert(True)
  50. finally:
  51. wrapped_socket.close()
  52. def get_cert(addr, port, starttls):
  53. cert = establish_conn(addr, port, starttls)
  54. pem_cert = ssl.DER_cert_to_PEM_cert(cert)
  55. logger.debug("The certificate is:\n%s" % pem_cert)
  56. return cert
  57. def capitalize_and_colons(in_hash):
  58. in_hash = in_hash.upper()
  59. new_hash = in_hash[0:2]
  60. for i in range(2, len(in_hash), 2):
  61. new_hash += ":" + in_hash[i:i+2]
  62. return new_hash
  63. def compute_fingerprints(cert, with_colons):
  64. thumb_md5 = hashlib.md5(cert).hexdigest()
  65. thumb_sha1 = hashlib.sha1(cert).hexdigest()
  66. thumb_sha256 = hashlib.sha256(cert).hexdigest()
  67. logger.info("MD5: " + thumb_md5)
  68. if with_colons:
  69. logger.info(" " + capitalize_and_colons(thumb_md5))
  70. logger.info("SHA1: " + thumb_sha1)
  71. if with_colons:
  72. logger.info(" " + capitalize_and_colons(thumb_sha1))
  73. logger.info("SHA256: " + thumb_sha256)
  74. if with_colons:
  75. logger.info(" " + capitalize_and_colons(thumb_sha256))
  76. @click.command()
  77. @click.argument('address')#, help="address to be used to retrieve the certificate")
  78. @click.option('-p', '--port', default=443, type=click.IntRange(1,65535), help="The port to connect to.")
  79. @click.option('--starttls', is_flag=True, flag_value=True, help="Whether to use starttls on connection.")
  80. @click.option('--debug/--nodebug', is_flag=True, flag_value=False, help="Debug output.")
  81. @click.option('-o', '--output', help="Path to save the certificate to.")
  82. @click.option('--colons/--nocolons', is_flag=True, flag_value=False, help="Whether to output also hashed with colons")
  83. def doit(address, port, starttls, debug, output, colons):
  84. if debug:
  85. logger.setLevel(logging.DEBUG)
  86. cert = get_cert(address, port, starttls)
  87. if output:
  88. with open(output, 'w') as f:
  89. logger.debug("Opening file %s" % output)
  90. f.write(ssl.DER_cert_to_PEM_cert(cert))
  91. logger.info("The certificate has been saved to %s" % output)
  92. compute_fingerprints(cert, colons)
  93. if __name__ == '__main__':
  94. doit()