from collections import OrderedDict, namedtuple
from enum import Enum
-# progress bar
+# progress bar and other console output fun
+STATE_WIDTH = 30
+
def terminal_size():
import fcntl, termios, struct
try:
return frac
def print_progress(state, fracs):
- STATE_WIDTH = 30
w, h = terminal_size()
if w < STATE_WIDTH+10: return # not a (wide enough) terminal
bar_width = w-STATE_WIDTH-3
w, h = terminal_size()
sys.stdout.write('\r'+(' '*w)+'\r')
sys.stdout.flush()
+
+class ConsoleFormat:
+ BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
+ RESET_SEQ = "\033[0m"
+ COLOR_SEQ = "\033[1;3%dm"
+ BOLD_SEQ = "\033[1m"
+ @staticmethod
+ def color(text, color):
+ return (ConsoleFormat.COLOR_SEQ % color) + text + ConsoleFormat.RESET_SEQ
+
# cipher check
def list_ciphers(spec="ALL:COMPLEMENTOFALL"):
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:
assert '\n' not in cipherInfo
cipherInfoFields = cipherInfo.split()
# get # of bits
- bitMatch = re.match(r'^Enc=[0-9A-Za-z]+\(([0-9]+)\)$', cipherInfoFields[4])
- if bitMatch is None:
+ 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]))
- bits = int(bitMatch.group(1))
+ 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])
+ 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])
+ 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', 'ECDH')
+ isPfs = kx in ('DH', 'DH(512)', 'ECDH')
# determine security level
isExp = cipher in self.exp
isLow = cipher in self.low
for cipher, supported in ciphers.items():
if supported:
cipherProps = propsProvider.getProps(cipher)
- print(" {0} ({1}, {2} bits, {3})".format(cipher, cipherProps.strength.name, cipherProps.bits, "FS" if cipherProps.isPfs else "not FS"))
+ 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))
print()