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?
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.