-#!/usr/bin/python3
+#!/usr/bin/env python3
import subprocess, sys, argparse, time, re
from collections import OrderedDict, namedtuple
from enum import Enum
try:
if cipher is not None:
options = ["-cipher", cipher]+options
- subprocess.check_call(["openssl", "s_client", "-"+protocol, "-connect", host+":"+str(port)]+options,
+ subprocess.check_call(["openssl", "s_client", "-"+protocol, "-connect", host+":"+str(port), "-servername", host]+options,
stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
except subprocess.CalledProcessError:
return False
# cipher classification
class CipherStrength(Enum):
unknown = -1
- exp = 0
- low = 1
- medium = 2
high = 3
def colorName(self):
- if self == CipherStrength.unknown:
- return self.name
- elif self.value == CipherStrength.high.value:
+ if self.value == CipherStrength.high.value:
return ConsoleFormat.color(self.name, ConsoleFormat.GREEN)
- elif self.value == CipherStrength.medium.value:
- return ConsoleFormat.color(self.name, ConsoleFormat.YELLOW)
else:
- return ConsoleFormat.color(self.name, ConsoleFormat.RED)
+ return ConsoleFormat.color(self.name, ConsoleFormat.YELLOW)
CipherProps = namedtuple('CipherProps', 'bits, strength, isPfs')
class CipherPropsProvider:
def __init__(self):
- self.exp = set(list_ciphers("EXP"))
- self.low = set(list_ciphers("LOW"))
- self.medium = set(list_ciphers("MEDIUM"))
self.high = set(list_ciphers("HIGH"))
self.props = {}
- def __getProps(self, cipher):
+ def getProps(self, protocol, cipher):
+ # strip the sub-version-number from the protocol
+ pos = protocol.find('_')
+ if pos >= 0:
+ protocol = protocol[:pos]
# as OpenSSL about this cipher
- cipherInfo = subprocess.check_output(["openssl", "ciphers", "-v", cipher]).decode('UTF-8').strip()
- assert '\n' not in cipherInfo
- cipherInfoFields = cipherInfo.split()
+ cipherInfo = subprocess.check_output(["openssl", "ciphers", "-v", "-"+protocol, cipher]).decode('UTF-8').strip()
+ cipherInfoFields = None
+ for line in cipherInfo.split('\n'):
+ line = line.split()
+ if line[0] == cipher:
+ cipherInfoFields = line
+ break
+ if cipherInfoFields is None:
+ raise Exception("Cannot determine cipher properties of {0} (protocol: {1})".format(cipher, protocol))
# get # of bits
encMatch = re.match(r'^Enc=([0-9A-Za-z]+)\(([0-9]+)\)$', cipherInfoFields[4])
if encMatch is None:
kx = kxMatch.group(1)
isPfs = kx in ('DH', 'DH(512)', 'ECDH')
# determine security level
- isExp = cipher in self.exp
- isLow = cipher in self.low
- isMedium = cipher in self.medium
- isHigh = cipher in self.high
- assert isExp+isLow+isMedium+isHigh <= 1, "Cipher is more than one from EXP, LOW, MEDIUM, HIGH"
- if isExp:
- strength = CipherStrength.exp
- elif isLow:
- strength = CipherStrength.low
- elif isMedium:
- strength = CipherStrength.medium
- elif isHigh:
+ if cipher in self.high:
strength = CipherStrength.high
else:
strength = CipherStrength.unknown
# done!
return CipherProps(bits=bits, strength=strength, isPfs=isPfs)
-
- def getProps(self, cipher):
- if cipher in self.props:
- return self.props[cipher]
- props = self.__getProps(cipher)
- self.props[cipher] = props
- return props
# main program
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Check TLS ciphers supported by a host')
parser.add_argument("--starttls", dest="starttls",
- help="Use a STARTTLS variant to establish the TLS connection. Possible values include smpt, imap, xmpp.")
+ help="Use a STARTTLS variant to establish the TLS connection. Possible values include smtp, imap.")
parser.add_argument("--wait-time", "-t", dest="wait_time", default="10",
help="Time (in ms) to wait between two connections to the server. Default is 10ms.")
parser.add_argument("host", metavar='HOST[:PORT]',
else:
for cipher, supported in ciphers.items():
if supported:
- cipherProps = propsProvider.getProps(cipher)
+ cipherProps = propsProvider.getProps(protocol, cipher)
fsText = ConsoleFormat.color("FS", ConsoleFormat.GREEN) if cipherProps.isPfs else ConsoleFormat.color("no FS", ConsoleFormat.RED)
bitColor = ConsoleFormat.GREEN if cipherProps.bits >= 128 else (ConsoleFormat.YELLOW if cipherProps.bits >= 100 else ConsoleFormat.RED)
print(" {0} ({1}, {2}, {3})".format(cipher.ljust(STATE_WIDTH), cipherProps.strength.colorName(), ConsoleFormat.color(str(cipherProps.bits)+" bits", bitColor), fsText))