+# 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:
+ 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)
+
+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):
+ # 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()
+ # get # of bits
+ encMatch = re.match(r'^Enc=([0-9A-Za-z]+)\(([0-9]+)\)$', cipherInfoFields[4])
+ if encMatch is None:
+ raise Exception("Unexpected OpenSSL output: Cannot determine encryption strength from {1}\nComplete output: {0}".format(cipherInfo, cipherInfoFields[4]))
+ encCipher = encMatch.group(1)
+ bits = int(encMatch.group(2))
+ if encCipher == '3DES':
+ # OpenSSL gives the key size, which however for 3DES is a totally bad estimate
+ bits = int(bits*2/3)
+ # figure out whether the cipher is pfs
+ kxMatch = re.match(r'^Kx=([0-9A-Z/()]+)$', cipherInfoFields[2])
+ if kxMatch is None:
+ raise Exception("Unexpected OpenSSL output: Cannot determine key-exchange method from {1}\nComplete output: {0}".format(cipherInfo, cipherInfoFields[2]))
+ 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:
+ 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