user9371654
user9371654

Reputation: 2408

How to get the TLS client supported TLS versions in python ssl

In python ssl, one can configure the TLS client's ciphersuites and versions. The ciphersuites are set using context.set_ciphers(ciphers) and the versions using context.options.

To make sure from the setup, one can get the ciphers in a client (even before the handshake, this is for setting up the client) using context.get_ciphers().

My question: how can I get the client's supported protocols. Please note that I am not using the default versions. I changed them by excluding some versions using context.options. For example, this statement excludes TLS 1.1 from my client:

context.options |= ssl.OP_NO_TLSv1_1

I want to make sure form my client TLS versions in the same way as I did in the ciphers using context.get_ciphers(). Is there any way I can do so?

Upvotes: 5

Views: 20976

Answers (1)

CristiFati
CristiFati

Reputation: 41167

The functionality you're after is available (at least partially) in Python 3.6 (and newer). Check [Python.Docs]: ssl - TLS/SSL wrapper for socket objects for more details:

>>> import ssl
>>> import sys
>>>
>>> "Python {:s} on {:s}".format(sys.version, sys.platform)
'Python 3.6.5 (v3.6.5:f59c0932b4, Mar 28 2018, 17:00:18) [MSC v.1900 64 bit (AMD64)] on win32'
>>> ctx0 = ssl.create_default_context()
>>> ctx0.options
<Options.OP_NO_SSLv3|OP_NO_SSLv2|OP_CIPHER_SERVER_PREFERENCE|OP_SINGLE_DH_USE|OP_SINGLE_ECDH_USE|OP_NO_COMPRESSION|OP_ALL: -2091252737>

For older versions, some code (which also works on newer ones) is required.

code00.py:

#!/usr/bin/env python

import ssl
import sys
from pprint import pprint as pp


__PROTO_TAG = "PROTOCOL_"
__OP_NO_TAG = "OP_NO_"
__OP_NO_TAG_LEN = len(__OP_NO_TAG)
_PROTOS_DATA = list()
for item_name in dir(ssl):
    if item_name.startswith(__OP_NO_TAG) and item_name[-1].isdigit():
        op_no_item = getattr(ssl, item_name)
        if op_no_item:
            proto_name = item_name[__OP_NO_TAG_LEN:]
            _PROTOS_DATA.append((proto_name, getattr(ssl, __PROTO_TAG + proto_name, -1), op_no_item))
del __OP_NO_TAG_LEN
del __OP_NO_TAG
del __PROTO_TAG


def get_protocols(ctx):
    supported_classes = (ssl.SSLContext,)
    if not isinstance(ctx, supported_classes):
        raise TypeError("Argument must be an instance of `{:}`".format(supported_classes[0] if len(supported_classes) == 1 else supported_classes))
    protocols = list()
    for proto_data in _PROTOS_DATA:
        if ctx.options & proto_data[-1] != proto_data[-1]:
            protocols.append(proto_data[:-1])
    return protocols


def print_data(ctx):
    print("Options: {:08X} ({!r})".format(ctx.options, ctx.options))
    print("Protocols:")
    for proto in get_protocols(ctx):
        print("    {:s} - {:d}".format(*proto))
    print()


def main(*argv):
    print("{:s}\n".format(ssl.OPENSSL_VERSION))
    ctx0 = ssl.create_default_context()
    print_data(ctx0)
    print("--- Removing TLSv1_1...")
    ctx0.options |= ssl.OP_NO_TLSv1_1
    print_data(ctx0)
    print("--- Adding SSLv3...")
    ctx0.options -= ssl.OP_NO_SSLv3  # !!! N.B.: Due to the fact that ssl.OP_NO_* flags only have one bit set, this works, but DON'T DO IT !!!
    print_data(ctx0)
    print("\nComputed protocols:")
    pp([item[:-1] + (hex(item[-1]),) for item in _PROTOS_DATA])


if __name__ == "__main__":
    print(
        "Python {:s} {:03d}bit on {:s}\n".format(
            " ".join(elem.strip() for elem in sys.version.split("\n")),
            64 if sys.maxsize > 0x100000000 else 32,
            sys.platform,
        )
    )
    rc = main(*sys.argv[1:])
    print("\nDone.\n")
    sys.exit(rc)

Notes:

  • As I worked extensively in this area, I have lots of Python versions on a variety of OSes, built against various OpenSSL versions (as seen in the outputs below)

  • Tried to keep everything as general as possible

  • Basing the code on (ssl) module attributes only; due to the fact that each Python version is built with a particular OpenSSL version, surprises might arise when using custom combinations (I could hardcode the OP_NO_* constants - which are consistent over OpenSSL versions, but that wouldn't be scalable)

  • There is the ssl module implementation (specific to Python version, which relies on a specific OpenSSL version - as stated above), plus the OpenSSL version (which might might not have some stuff) actually used to build the ssl module. That's why running the same code on various combinations, yields (slightly) different results (check outputs below)

  • On Win things are simpler, as (by default) OpenSSL is statically linked in _ssl.pyd (starting with Python 3.7, this no longer applies, the OpenSSL .dlls are also shipped as part of Python), but on Nix, the OpenSSL libs (that are installed on the system) are loaded at runtime

  • Code walkthrough:

    • _PROTOS_DATA - computed at module import time: it's a list of protocol entries, based on ssl module attributes. Each entry has 3 fields:

      • Protocol name

      • Protocol identifier in the ssl module (-1 if not present: e.g. SSLv2)

      • The OP_NO_ constant used to disable this protocol

      It's displayed at the end of each run for clarity

    • get_protocols - determines the "active" supported protocols for an ssl.SSLContext

    • print_data - helper function

Output:

  • Win 10 pc064

    [cfati@cfati-5510-0:e:\Work\Dev\StackOverflow\q049788677]> "C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python36_64\python.exe" ./code00.py
    Python 3.6.5 (v3.6.5:f59c0932b4, Mar 28 2018, 17:00:18) [MSC v.1900 64 bit (AMD64)] 064bit on win32
    
    OpenSSL 1.0.2k  26 Jan 2017
    
    Options: -7CA5FC01 (<Options.OP_NO_SSLv3|OP_NO_SSLv2|OP_CIPHER_SERVER_PREFERENCE|OP_SINGLE_DH_USE|OP_SINGLE_ECDH_USE|OP_NO_COMPRESSION|OP_ALL: -2091252737>)
    Protocols:
        TLSv1 - 3
        TLSv1_1 - 4
        TLSv1_2 - 5
    
    --- Removing TLSv1_1...
    Options: -6CA5FC01 (<Options.OP_NO_TLSv1_1|OP_NO_SSLv3|OP_NO_SSLv2|OP_CIPHER_SERVER_PREFERENCE|OP_SINGLE_DH_USE|OP_SINGLE_ECDH_USE|OP_NO_COMPRESSION|OP_ALL: -1822817281>)
    Protocols:
        TLSv1 - 3
        TLSv1_2 - 5
    
    --- Adding SSLv3...
    Options: -6EA5FC01 (<Options.OP_NO_TLSv1_1|OP_NO_SSLv2|OP_CIPHER_SERVER_PREFERENCE|OP_SINGLE_DH_USE|OP_SINGLE_ECDH_USE|OP_NO_COMPRESSION|OP_ALL: -1856371713>)
    Protocols:
        SSLv3 - 1
        TLSv1 - 3
        TLSv1_2 - 5
    
    
    Computed protocols:
    [('SSLv2', -1, '0x1000000'),
     ('SSLv3', <_SSLMethod.PROTOCOL_SSLv3: 1>, '0x2000000'),
     ('TLSv1', <_SSLMethod.PROTOCOL_TLSv1: 3>, '0x4000000'),
     ('TLSv1_1', <_SSLMethod.PROTOCOL_TLSv1_1: 4>, '0x10000000'),
     ('TLSv1_2', <_SSLMethod.PROTOCOL_TLSv1_2: 5>, '0x8000000')]
    
    Done.
    
    
    [cfati@cfati-5510-0:e:\Work\Dev\StackOverflow\q049788677]>
    [cfati@cfati-5510-0:e:\Work\Dev\StackOverflow\q049788677]> "e:\Work\Dev\VEnvs\py34x64_test\Scripts\python.exe" ./code00.py
    Python 3.4.4 (v3.4.4:737efcadf5a6, Dec 20 2015, 20:20:57) [MSC v.1600 64 bit (AMD64)] 064bit on win32
    
    OpenSSL 1.0.2d 9 Jul 2015
    
    Options: -7CFDFC01 (-2097019905)
    Protocols:
        TLSv1 - 3
        TLSv1_1 - 4
        TLSv1_2 - 5
    
    --- Removing TLSv1_1...
    Options: -6CFDFC01 (-1828584449)
    Protocols:
        TLSv1 - 3
        TLSv1_2 - 5
    
    --- Adding SSLv3...
    Options: -6EFDFC01 (-1862138881)
    Protocols:
        SSLv3 - 1
        TLSv1 - 3
        TLSv1_2 - 5
    
    
    Computed protocols:
    [('SSLv2', 0, '0x1000000'),
     ('SSLv3', 1, '0x2000000'),
     ('TLSv1', 3, '0x4000000'),
     ('TLSv1_1', 4, '0x10000000'),
     ('TLSv1_2', 5, '0x8000000')]
    
    Done.
    
    
    [cfati@cfati-5510-0:e:\Work\Dev\StackOverflow\q049788677]>
    [cfati@cfati-5510-0:e:\Work\Dev\StackOverflow\q049788677]> "c:\Install\x64\Python\Python\3.7\python.exe" ./code00.py
    Python 3.7.0b4 (v3.7.0b4:eb96c37699, May  2 2018, 19:02:22) [MSC v.1913 64 bit (AMD64)] 064bit on win32
    
    OpenSSL 1.1.0h  27 Mar 2018
    
    Options: -7DBDFFAC (<Options.OP_NO_SSLv3|OP_CIPHER_SERVER_PREFERENCE|OP_NO_COMPRESSION|OP_ALL: -2109603756>)
    Protocols:
        TLSv1 - 3
        TLSv1_1 - 4
        TLSv1_2 - 5
    
    --- Removing TLSv1_1...
    Options: -6DBDFFAC (<Options.OP_NO_TLSv1_1|OP_NO_SSLv3|OP_CIPHER_SERVER_PREFERENCE|OP_NO_COMPRESSION|OP_ALL: -1841168300>)
    Protocols:
        TLSv1 - 3
        TLSv1_2 - 5
    
     --- Adding SSLv3...
    Options: -6FBDFFAC (<Options.OP_NO_TLSv1_1|OP_CIPHER_SERVER_PREFERENCE|OP_NO_COMPRESSION|OP_ALL: -1874722732>)
    Protocols:
        SSLv3 - -1
        TLSv1 - 3
        TLSv1_2 - 5
    
    
    Computed protocols:
    [('SSLv3', -1, '0x2000000'),
     ('TLSv1', <_SSLMethod.PROTOCOL_TLSv1: 3>, '0x4000000'),
     ('TLSv1_1', <_SSLMethod.PROTOCOL_TLSv1_1: 4>, '0x10000000'),
     ('TLSv1_2', <_SSLMethod.PROTOCOL_TLSv1_2: 5>, '0x8000000')]
    
    Done.
    
  • OSX 9 pc064:

    [cfati@cfati-macosx9x64-1:~/Work/Dev/StackOverflow/q049788677]> python ./code00.py
    Python 2.7.10 (default, Oct 14 2015, 05:51:29)
    [GCC 4.8.2] 064bit on darwin
    
    OpenSSL 1.0.1p-fips 9 Jul 2015
    
    Options: 830203FF (2197947391L)
    Protocols:
        TLSv1 - 3
        TLSv1_1 - 4
        TLSv1_2 - 5
    ()
    --- Removing TLSv1_1...
    Options: 930203FF (2466382847L)
    Protocols:
        TLSv1 - 3
        TLSv1_2 - 5
    ()
    --- Adding SSLv3...
    Options: 910203FF (2432828415L)
    Protocols:
        SSLv3 - 1
        TLSv1 - 3
        TLSv1_2 - 5
    ()
    
    Computed protocols:
    [('SSLv2', 0, '0x1000000'),
     ('SSLv3', 1, '0x2000000'),
     ('TLSv1', 3, '0x4000000'),
     ('TLSv1_1', 4, '0x10000000'),
     ('TLSv1_2', 5, '0x8000000')]
    
    Done.
    
  • Ubuntu:

    • 16 pc064:

      [cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q049788677]> python3 ./code00.py
      Python 3.5.2 (default, Nov 23 2017, 16:37:01)
      [GCC 5.4.0 20160609] pc064 on linux
      
      OpenSSL 1.0.2g  1 Mar 2016
      
      Options: 830203FF (2197947391)
      Protocols:
          TLSv1 - 3
          TLSv1_1 - 4
          TLSv1_2 - 5
      
      --- Removing TLSv1_1...
      Options: 930203FF (2466382847)
      Protocols:
          TLSv1 - 3
          TLSv1_2 - 5
      
      --- Adding SSLv3...
      Options: 930203FF (2466382847)
      Protocols:
          TLSv1 - 3
          TLSv1_2 - 5
      
      
      Computed protocols:
      [('SSLv2', -1, '0x1000000'),
       ('SSLv3', -1, '0x2000000'),
       ('TLSv1', <_SSLMethod.PROTOCOL_TLSv1: 3>, '0x4000000'),
       ('TLSv1_1', <_SSLMethod.PROTOCOL_TLSv1_1: 4>, '0x10000000'),
       ('TLSv1_2', <_SSLMethod.PROTOCOL_TLSv1_2: 5>, '0x8000000')]
      
      Done.
      
      [cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q049788677]>
      [cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q049788677]> LD_LIBRARY_PATH=../q049493537/Python-3.6.4:../q049320993/ssl/build/lib ../q049493537/Python-3.6.4/python ./code00.py
      Python 3.6.4 (default, Mar 28 2018, 23:34:25)
      [GCC 5.4.0 20160609] pc064 on linux
      
      OpenSSL 1.0.2h-fips  3 May 2016
      
      Options: 835A03FF (<Options.OP_ALL|OP_NO_SSLv3|OP_NO_SSLv2|OP_CIPHER_SERVER_PREFERENCE|OP_SINGLE_DH_USE|OP_SINGLE_ECDH_USE|OP_NO_COMPRESSION: 2203714559>)
      Protocols:
          TLSv1 - 3
          TLSv1_1 - 4
          TLSv1_2 - 5
      
      --- Removing TLSv1_1...
      Options: 935A03FF (<Options.OP_ALL|OP_NO_TLSv1_1|OP_NO_SSLv3|OP_NO_SSLv2|OP_CIPHER_SERVER_PREFERENCE|OP_SINGLE_DH_USE|OP_SINGLE_ECDH_USE|OP_NO_COMPRESSION: 2472150015>)
      Protocols:
          TLSv1 - 3
          TLSv1_2 - 5
      
      --- Adding SSLv3...
      Options: 915A03FF (<Options.OP_ALL|OP_NO_TLSv1_1|OP_NO_SSLv2|OP_CIPHER_SERVER_PREFERENCE|OP_SINGLE_DH_USE|OP_SINGLE_ECDH_USE|OP_NO_COMPRESSION: 2438595583>)
      Protocols:
          SSLv3 - -1
          TLSv1 - 3
          TLSv1_2 - 5
      
      
      Computed protocols:
      [('SSLv2', -1, '0x1000000'),
       ('SSLv3', -1, '0x2000000'),
       ('TLSv1', <_SSLMethod.PROTOCOL_TLSv1: 3>, '0x4000000'),
       ('TLSv1_1', <_SSLMethod.PROTOCOL_TLSv1_1: 4>, '0x10000000'),
       ('TLSv1_2', <_SSLMethod.PROTOCOL_TLSv1_2: 5>, '0x8000000')]
      
      Done.
      
    • 22 pc064:

      (qaic-env) [cfati@cfati-5510-0:/mnt/e/Work/Dev/StackExchange/StackOverflow/q049788677]> LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/pc064/openssl/openssl/3.0.14/lib python ./code00.py
      Python 3.8.19 (default, Apr  6 2024, 17:58:10) [GCC 11.4.0] 064bit on linux
      
      OpenSSL 3.0.14 4 Jun 2024
      
      Options: 825200D0 (<Options.OP_ALL|OP_NO_SSLv3|OP_CIPHER_SERVER_PREFERENCE|OP_ENABLE_MIDDLEBOX_COMPAT|OP_NO_COMPRESSION|OP_IGNORE_UNEXPECTED_EOF: 2186412240>)
      Protocols:
          TLSv1 - 3
          TLSv1_1 - 4
          TLSv1_2 - 5
          TLSv1_3 - -1
      
      --- Removing TLSv1_1...
      Options: 925200D0 (<Options.OP_ALL|OP_NO_TLSv1_1|OP_NO_SSLv3|OP_CIPHER_SERVER_PREFERENCE|OP_ENABLE_MIDDLEBOX_COMPAT|OP_NO_COMPRESSION|OP_IGNORE_UNEXPECTED_EOF:         >    2454847696>)
      Protocols:
          TLSv1 - 3
          TLSv1_2 - 5
          TLSv1_3 - -1
      
      --- Adding SSLv3...
      Options: 905200D0 (<Options.OP_ALL|OP_NO_TLSv1_1|OP_CIPHER_SERVER_PREFERENCE|OP_ENABLE_MIDDLEBOX_COMPAT|OP_NO_COMPRESSION|OP_IGNORE_UNEXPECTED_EOF: 2421293264>)
      Protocols:
          SSLv3 - -1
          TLSv1 - 3
          TLSv1_2 - 5
          TLSv1_3 - -1
      
      
      Computed protocols:
      [('SSLv3', -1, '0x2000000'),
       ('TLSv1', <_SSLMethod.PROTOCOL_TLSv1: 3>, '0x4000000'),
       ('TLSv1_1', <_SSLMethod.PROTOCOL_TLSv1_1: 4>, '0x10000000'),
       ('TLSv1_2', <_SSLMethod.PROTOCOL_TLSv1_2: 5>, '0x8000000'),
       ('TLSv1_3', -1, '0x20000000')]
      
      Done.
      
      (qaic-env) [cfati@cfati-5510-0:/mnt/e/Work/Dev/StackExchange/StackOverflow/q049788677]> 
      (qaic-env) [cfati@cfati-5510-0:/mnt/e/Work/Dev/StackExchange/StackOverflow/q049788677]> LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/pc032/openssl/openssl/3.3.1/lib /usr/local/pc032/python/python/3.10.13/bin/python ./code00.py 
      Python 3.10.13 (tags/v3.10.13-dirty:49965601d6, Sep 12 2023, 19:27:34) [GCC 11.4.0] 032bit on linux
      
      OpenSSL 3.3.1 4 Jun 2024
      
      Options: -7DADFFB0 (<Options.OP_NO_SSLv3|OP_CIPHER_SERVER_PREFERENCE|OP_ENABLE_MIDDLEBOX_COMPAT|OP_NO_COMPRESSION|OP_ALL: -2108555184>)
      Protocols:
          TLSv1 - 3
          TLSv1_1 - 4
          TLSv1_2 - 5
          TLSv1_3 - -1
      
      --- Removing TLSv1_1...
      /mnt/e/Work/Dev/StackExchange/StackOverflow/q049788677/./code00.py:47: DeprecationWarning: ssl.OP_NO_SSL*/ssl.OP_NO_TLS* options are deprecated
        ctx0.options |= ssl.OP_NO_TLSv1_1
      Options: -6DADFFB0 (<Options.OP_NO_TLSv1_1|OP_NO_SSLv3|OP_CIPHER_SERVER_PREFERENCE|OP_ENABLE_MIDDLEBOX_COMPAT|OP_NO_COMPRESSION|OP_ALL: -1840119728>)
      Protocols:
          TLSv1 - 3
          TLSv1_2 - 5
          TLSv1_3 - -1
      
      --- Adding SSLv3...
      Options: -6FADFFB0 (<Options.OP_NO_TLSv1_1|OP_CIPHER_SERVER_PREFERENCE|OP_ENABLE_MIDDLEBOX_COMPAT|OP_NO_COMPRESSION|OP_ALL: -1873674160>)
      Protocols:
          SSLv3 - -1
          TLSv1 - 3
          TLSv1_2 - 5
          TLSv1_3 - -1
      
      
      Computed protocols:
      [('SSLv3', -1, '0x2000000'),
       ('TLSv1', <_SSLMethod.PROTOCOL_TLSv1: 3>, '0x4000000'),
       ('TLSv1_1', <_SSLMethod.PROTOCOL_TLSv1_1: 4>, '0x10000000'),
       ('TLSv1_2', <_SSLMethod.PROTOCOL_TLSv1_2: 5>, '0x8000000'),
       ('TLSv1_3', -1, '0x20000000')]
      
      Done.
      

Upvotes: 5

Related Questions