more details in assertions
[tls-check.git] / tls-check
index 5e8e19e5e18de4bf467cc0e586aa89d57c9d2e63..8a64d84734c7daaabe45f3e9bf4eb914bc09cf0b 100755 (executable)
--- a/tls-check
+++ b/tls-check
@@ -53,7 +53,9 @@ def list_ciphers(spec="ALL:COMPLEMENTOFALL"):
     ciphers = subprocess.check_output(["openssl", "ciphers", spec]).decode('UTF-8').strip()
     return ciphers.split(':')
 
-def test_cipher(host, port, protocol, cipher = None, options=[]):
+def test_cipher(host, port, protocol, cipher = None, wait_time=0, options=[]):
+    # throttle
+    time.sleep(wait_time/1000)
     try:
         if cipher is not None:
             options = ["-cipher", cipher]+options
@@ -65,15 +67,13 @@ def test_cipher(host, port, protocol, cipher = None, options=[]):
         return True
 
 def test_protocol(host, port, protocol, ciphers, base_frac, wait_time=0, options=[]):
-    if test_cipher(host, port, protocol, options=options):
+    if test_cipher(host, port, protocol, wait_time=wait_time, options=options):
         # the protocol is supported
         results = OrderedDict()
         for i in range(len(ciphers)):
             cipher = ciphers[i]
             print_progress(protocol+" "+cipher, base_frac+[(i, len(ciphers))])
-            results[cipher] = test_cipher(host, port, protocol, cipher, options)
-            # throttle
-            time.sleep(wait_time/1000)
+            results[cipher] = test_cipher(host, port, protocol, cipher=cipher, wait_time=wait_time, options=options)
         return results
     else:
         # it is not supported
@@ -121,13 +121,17 @@ class CipherPropsProvider:
     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
+        assert '\n' not in cipherInfo, "Cipher "+cipher+" produced unexpected output:\n"+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])
         if kxMatch is None:
@@ -139,7 +143,7 @@ class CipherPropsProvider:
         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"
+        assert isExp+isLow+isMedium+isHigh <= 1, "Cipher "+cipher+" is more than one from EXP, LOW, MEDIUM, HIGH"
         if isExp:
             strength = CipherStrength.exp
         elif isLow:
@@ -164,7 +168,7 @@ class CipherPropsProvider:
 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 smpt, 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]',
@@ -198,6 +202,6 @@ if __name__ == "__main__":
                 if supported:
                     cipherProps = propsProvider.getProps(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)
+                    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()