work with SNI
[tls-check.git] / tls-check
index f757bdbf535acf718e466315228aa0bc90193d7b..8be067e5e678a2ca01dc20c99946b3e91b179020 100755 (executable)
--- a/tls-check
+++ b/tls-check
@@ -59,7 +59,7 @@ def test_cipher(host, port, protocol, cipher = None, wait_time=0, options=[]):
     try:
         if cipher is not None:
             options = ["-cipher", cipher]+options
     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
                               stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
     except subprocess.CalledProcessError:
         return False
@@ -118,10 +118,14 @@ class CipherPropsProvider:
         self.high = set(list_ciphers("HIGH"))
         self.props = {}
     
         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
         # as OpenSSL about this cipher
-        cipherInfo = subprocess.check_output(["openssl", "ciphers", "-v", cipher]).decode('UTF-8').strip()
-        assert '\n' not in cipherInfo
+        cipherInfo = subprocess.check_output(["openssl", "ciphers", "-v", "-"+protocol, cipher]).decode('UTF-8').strip()
+        assert '\n' not in cipherInfo, "Cipher "+cipher+" produced unexpected output:\n"+cipherInfo
         cipherInfoFields = cipherInfo.split()
         # get # of bits
         encMatch = re.match(r'^Enc=([0-9A-Za-z]+)\(([0-9]+)\)$', cipherInfoFields[4])
         cipherInfoFields = cipherInfo.split()
         # get # of bits
         encMatch = re.match(r'^Enc=([0-9A-Za-z]+)\(([0-9]+)\)$', cipherInfoFields[4])
@@ -143,7 +147,7 @@ class CipherPropsProvider:
         isLow = cipher in self.low
         isMedium = cipher in self.medium
         isHigh = cipher in self.high
         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:
         if isExp:
             strength = CipherStrength.exp
         elif isLow:
@@ -156,19 +160,12 @@ class CipherPropsProvider:
             strength = CipherStrength.unknown
         # done!
         return CipherProps(bits=bits, strength=strength, isPfs=isPfs)
             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",
 
 # 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]',
     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]',
@@ -200,7 +197,7 @@ if __name__ == "__main__":
         else:
             for cipher, supported in ciphers.items():
                 if supported:
         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))
                     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))