🌐
Python
docs.python.org › 3 › library › ssl.html
ssl — TLS/SSL wrapper for socket objects — Python 3.14.4 ...
If ca_certs is specified, it should ... in SSLContext.load_verify_locations(). The call will attempt to validate the server certificate against that set of root certificates, and will fail if the validation attempt fails. A timeout can be specified with the timeout parameter. Changed in version 3.3: This function is now IPv6-compatible. Changed in version 3.5: The default ssl_version is changed from PROTOCOL_SSLv3 to PROTOCOL_TLS for maximum ...
🌐
Pythontic
pythontic.com › ssl › sslcontext › sslcontext
SSLcontext() method of SSLcontext class in Python | Pythontic.com
SSLContext(protocol=ssl.PROTOCOL_TLS); protocol – The SSL version to be used. An object of type SSLContext. In a Python program, an instance of the class ssl.SSLContext acts as a placeholder where the policies and artifacts related to the secure communication of a client or a server can be stored.
🌐
Electricmonk
electricmonk.nl › log › 2018 › 06 › 02 › ssl-tls-client-certificate-verification-with-python-v3-4-sslcontext
SSL/TLS client certificate verification with Python v3.4+ SSLContext | Electricmonk.nl weblog
June 2, 2018 - Since Python v3.4, the more secure, and thus preferred method of wrapping a socket in the SSL/TLS layer is to create an SSLContext instance and call SSLContext.wrap_socket(). However, the SSLContext.wrap_socket() method does not have the ca_certs parameter.
🌐
Python
docs.python.org › 3.3 › library › ssl.html
18.2. ssl — TLS/SSL wrapper for socket objects — Python 3.3.7 documentation
For more sophisticated applications, the ssl.SSLContext class helps manage settings and certificates, which can then be inherited by SSL sockets created through the SSLContext.wrap_socket() method.
🌐
OpenDev
opendev.org › opendev › gear › commit › 66ba8442dcaba24de0322793876bd241858b49a3
Create SSL context using PROTOCOL_TLS, fallback to highest supported version · 66ba8442dc - gear - OpenDev: Free Software Needs Free Tools
This moves from the 80 bit security level to the 112 bit security level and will require 2048 bit or larger RSA and DHE keys, 224 bit or larger ECC keys, SHA-2, TLSv1.2 or DTLSv1.2. Allowing to negotiate TLS to the highest available version between server and client solves the issue, provided that TLSv1.2 is useable. The option is supported by in the latest version of all pythons >=3.5 [1][2][3]. Unfortunately Xenial doesn't have latest 3.5 and lacks the ssl.PROTOCOL_TLS definition.
🌐
MicroPython
docs.micropython.org › en › latest › library › ssl.html
ssl – SSL/TLS module — MicroPython latest documentation
Wrap the given sock and return a new wrapped-socket object. The implementation of this function is to first create an SSLContext and then call the SSLContext.wrap_socket method on that context object. The arguments sock, server_side and server_hostname are passed through unchanged to the method ...
🌐
Python.org
discuss.python.org › core development
`ssl`: changing the default `SSLContext.verify_flags`? - Core Development - Discussions on Python.org
July 25, 2023 - At the moment (CPython 3.11), the default SSLContext.verify_flags is set to just VERIFY_X509_TRUSTED_FIRST: >>> import ssl >>> ssl.create_default_context().verify_flags <VerifyFlags.VERIFY_X509_TRUSTED_FIRST: 32768> I…
🌐
Readthedocs
python-security.readthedocs.io › ssl.html
Python SSL and TLS security — Python Security 0.0 documentation
New in Python 3.2. ... SSLContext.load_verify_locations(): This method can also load certification revocation lists (CRLs) in PEM or DER format.
🌐
CodeQL
codeql.github.com › codeql-query-help › python › py-insecure-protocol
Use of insecure SSL/TLS version — CodeQL query help documentation
import ssl import socket # Using the deprecated ssl.wrap_socket method ssl.wrap_socket(socket.socket(), ssl_version=ssl.PROTOCOL_SSLv2) # Using SSLContext context = ssl.SSLContext(ssl_version=ssl.PROTOCOL_SSLv3) # Using pyOpenSSL from pyOpenSSL import SSL context = SSL.Context(SSL.TLSv1_METHOD) All cases should be updated to use a secure protocol, such as PROTOCOL_TLSv1_2. Note that ssl.wrap_socket has been deprecated in Python 3.7.
Top answer
1 of 2
17

The ssl module in Python 2.6 supports up to TLS 1.0 only. If you do not wish to introduce additional dependencies (such as pyOpenSSL as you suggest) you will need to upgrade to Python 2.7 or 3.x to get support for newer versions of TLS.

To force a particular version of TLS in Python 2.7.9 or later, construct an SSLContext with the appropriate PROTOCOL_* constant. You can then use it with any API that lets you provide your own SSLContext.

import ssl
import urllib2

ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
# set other SSLContext options you might need
response = urllib2.urlopen(url, context=ctx)

To use a particular protocol version or higher (including future versions), use ssl.PROTOCOL_SSLv23 and then disable the protocol versions you do not want to use:

ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)

# allow TLS 1.2 and later
ctx.options |= ssl.OP_NO_SSLv2
ctx.options |= ssl.OP_NO_SSLv3
ctx.options |= ssl.OP_NO_TLSv1
ctx.options |= ssl.OP_NO_TLSv1_1

As for using a custom SSLContext with Requests in order to force a particular protocol version, according to the documentation there does not seem to be a way to do this, see the following example from the docs.

2 of 2
0

If upgrading the code isn't an option, you should be able to proxy your connection using a squid server or nginx. Here's a squid "bump" method example:

Can I use Squid to upgrade client TLS connections?

Another option is to keep the proxy but rewrite the URL (http to https) and have your application send requests to http (seems odd but works if implemented right).

Find elsewhere
🌐
Pyopenssl
pyopenssl.org › en › latest › api › ssl.html
SSL — An interface to the SSL-specific parts of OpenSSL — pyOpenSSL 26.0.0 documentation
Specify the protocols that the client is prepared to speak after the TLS connection has been negotiated using Application Layer Protocol Negotiation. ... protos – A list of the protocols to be offered to the server. This list should be a Python list of bytestrings representing the protocols ...
🌐
ProgramCreek
programcreek.com › python › example › 72757 › ssl.SSLContext
Python Examples of ssl.SSLContext
def test_good_certificate(): # Disable due to https://github.com/urllib3/urllib3/issues/497 requests.packages.urllib3.disable_warnings( requests.packages.urllib3.exceptions.SubjectAltNameWarning) ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) ssl_context.load_cert_chain('tests/ssl/goodkey.crt', 'tests/ssl/goodkey.key') server = Process(target=run_simple_https_server, args=(ssl_context,)) server.start() time.sleep(0.5) try: s = requests.Session() s.mount('https://localhost:8080/', HTTPSAdapter()) r = s.get('https://localhost:8080/', verify='tests/ssl/rootCA.crt') assert r.text == 'Passed' server.terminate() except Exception: server.terminate() raise ·
🌐
Python
docs.python.domainunion.de › 3 › library › ssl.html
ssl — TLS/SSL wrapper for socket objects — Python 3.14.3 documentation
If ca_certs is specified, it should ... in SSLContext.load_verify_locations(). The call will attempt to validate the server certificate against that set of root certificates, and will fail if the validation attempt fails. A timeout can be specified with the timeout parameter. Changed in version 3.3: This function is now IPv6-compatible. Changed in version 3.5: The default ssl_version is changed from PROTOCOL_SSLv3 to PROTOCOL_TLS for maximum ...
Top answer
1 of 1
5

Well,

First

You should know ssl or today called tls, is a handshake "agree each other" process, at first open of the TCP socket, then the communication through this socket will be hiddenly encrypted or you can sniff with wireshark off course, because of a "wrapping" of the socket which is the normal method used when using python or other languages i mean just an intermediate class doing the encription for you and you don't even take care, finally the shared encripted-suite used between client and server was previously "agreed" on this mentioned handshake process.

Second

HTTP is just another application layer over TCP but the real test will be always over Transport layer and i'll share my own tls/ssl program-tester on the next lines to mitigate theory flaws.

You can run it like python3.x.exe program.py -ip www.whatever.com -puerto 443 BUT you should put two files next to this program.py, the local.crt and local.key files as defaults for my program, however you can always specify them with option -tls ../location/other.crt ../location/other.key

The reasons for local.crt and local.key are basically the handshake is a process of an interchange of your keys (local.xxx files) and the remote server files ALSO, SO in my next tester example, i have always custom ssl context variable based on those local.xxx files, please note use of .load_cert_chain on it

Anyways you can generate them very easily using openssl command like this openssl req -newkey rsa:2048 -new -x509 -nodes -days 3650 -keyout llave.key -out cert.crt -subj "/C=CO/ST=ANT/L=Medellin/O=YourCompany/OU=YourArea/CN=YourDevice/[email protected]" or a website similar to https://www.ssl.com/online-csr-and-key-generator/ just save them on separate .crt and .key file of yours

import os, platform, socket, ssl
if platform.system().lower() == 'linux':    # Linux OS ..
    windows = False
    slash_principal = '/'
    slash_secundario = '\\'
    limpiar_pantalla = lambda :os.system('clear')
else:   # Windows OS ..
    windows = True
    slash_principal = '\\'
    slash_secundario = '/'
    limpiar_pantalla = lambda :os.system('cls')

import argparse, errno
limpiar_pantalla()
filepath = os.path.dirname(os.path.abspath(__file__))
ssl_files_def = [filepath+slash_principal+'local.crt', filepath+slash_principal+'local.key']

parser = argparse.ArgumentParser( description="Probador de TLS")
parser.add_argument( '-ip', nargs=1, metavar='IP', default=['www.google.com'], type=str, help=' IP del socket TCP a probar')    # 34.196.130.67 EPSA Pruebas
parser.add_argument( '-puerto', nargs=1, metavar='PUERTO', default=[443], type=int, help=' Puerto del socket TCP a probar')
parser.add_argument( '-version', nargs=1, metavar='TLS_VERSION', type=int, choices=[0,1,2,3], help=' Version TLS a probar si no se especifica se hara AUTO')
parser.add_argument( '-tls', nargs=2, metavar=('CRT', 'KEY'), default=ssl_files_def, type=str, help=' Ruta al archivo ".crt" ..y.. ruta al archivo ".key" locales, para ejecutar el handshake/intercambio TLS con alguien remoto')
parser_opts = parser.parse_args()
#print("Argumentos -->", parser_opts._get_kwargs())
#print()


# ---------------- Logica ---------------- #

sock_pair = ( parser_opts.ip[0], parser_opts.puerto[0] )
sock_tls_ver = ssl.PROTOCOL_TLS     # Auto
if parser_opts.version:             # Se ingreso en el parse? (!= None) ya que no es obligatoria
    if parser_opts.version[0] == 0:
        sock_tls_ver = ssl.PROTOCOL_TLSv1
    elif parser_opts.version[0] == 1:
        sock_tls_ver = ssl.PROTOCOL_TLSv1_1
    elif parser_opts.version[0] == 2:
        sock_tls_ver = ssl.PROTOCOL_TLSv1_2
    #elif parser_opts.version[0] == 3:
    #   sock_tls_ver = ssl.PROTOCOL_TLSv1_3

sslCntx = ssl.SSLContext(sock_tls_ver)  # https://docs.python.org/3/library/ssl.html#ssl.SSLContext
# Con las siguientes opciones se evitan suites SSL inseguras (Al final solo permitira >= TLSv1..)
sslCntx.options |= ssl.OP_NO_SSLv2
sslCntx.options |= ssl.OP_NO_SSLv3
if parser_opts.version:             # Se ingreso en el parse? (!= None) ya que no es obligatoria
    if parser_opts.version[0] >= 1:
        sslCntx.options |= ssl.OP_NO_TLSv1      # Evite la version 0 de TLS al conectar/handshake (version >= v1.1) 
    if parser_opts.version[0] >= 2:
        sslCntx.options |= ssl.OP_NO_TLSv1_1    # Evite la version 1 de TLS al conectar/handshake (version >= v1.2) 
    if parser_opts.version[0] >= 3:
        sslCntx.options |= ssl.OP_NO_TLSv1_2    # Evite la version 2 de TLS al conectar/handshake (version >= v1.3)
sslCntx.load_cert_chain(*ssl_files_def)         # Finalmente cargue mis llaves con las que hare el handshake.

print()
print('Versiones SSL (Obsoleto, demostrativo):', int(ssl.PROTOCOL_SSLv23))  # OBSOLETO HACE RATO.
print('Versiones TLS:', int(ssl.PROTOCOL_TLSv1), int(ssl.PROTOCOL_TLSv1_1), int(ssl.PROTOCOL_TLSv1_2))#, ssl.PROTOCOL_TLSv1_3)
print('Mi Contexto:', sslCntx.options, int(sslCntx.minimum_version), int(sslCntx.maximum_version), sslCntx.verify_flags, sslCntx.verify_mode, sslCntx.get_ca_certs())#, sslCntx.get_ciphers(), dir(sslCntx))
print('Remoto:', sock_pair)
print()

s = socket.socket()
s_tls = sslCntx.wrap_socket(s)  # Wrap lo convierte en un socket que hara handshake TLS y la comunicacion sera encriptada por ello.
try:
    print('Socket Ok:', s_tls.version(), s_tls.session, s_tls.shared_ciphers() )#, dir(s_tls))
    print()
    s_tls.connect(sock_pair)
    
    print('Conexion Ok:', s_tls.version(), s_tls.session.timeout)
    print( s_tls.shared_ciphers() )#, dir(s_tls.session), dir(s_tls))
    print()
    s_tls.close()

except socket.error as e:
    print('Socket FALLO (', os.strerror(e.errno), ')')

except Exception as e:
    print('Conexion FALLO:', type(e).__name__, e)

print()

NOTE: this program can also be used to test whatever socket/page you want with specify tls version as an option -version 0, -version 1, -version 2, -version 3 to force a specific TLS version and is useful to detect downgradable (Insecure) servers

Third

Concepts clear, if you already want to test a TLS through application-layer python modules, you would want to put or specify this custom sslCntx (ssl context) variable of the previous code over this desired module so it can vary depending on the documentation of it.

I mean replace this ...

s = socket.socket()
s_tls = sslCntx.wrap_socket(s)  # Wrap lo convierte en un socket que hara handshake TLS y la comunicacion sera encriptada por ello.
try:
    print('Socket Ok:', s_tls.session, s_tls.version(), s_tls.shared_ciphers(), s_tls )#, dir(s_tls))
    print()
    s_tls.connect(sock_pair)
    
    print('Conexion Ok:', s_tls.session.timeout, s_tls.version(), s_tls.shared_ciphers(), s_tls )#, dir(s_tls.session), dir(s_tls))
    print()
    s_tls.close()

... With the specifics of your desired module, for example on http module will be the argument "context" like this ...

from http.client import HTTPSConnection
try:
    conn = HTTPSConnection(*sock_pair, context=sslCntx)
    print('HTTP Ok:', conn.host, conn.port)#, dir(conn))
    print()
    conn.request( 'GET', '/' )
    ans = conn.getresponse()
    print('GET', ans.reason, ':', ans.status)
    print(ans.headers)#, dir(ans))
    conn.close()
    print()

And run it like python3.x.exe program.py -ip www.whatever.com -puerto 443 -version 2 because on the question you asked to force the use of TLSv1.2

Fourth

On the other hand when NOT using -version option, the default python parameter "sock_tls_ver = ssl.PROTOCOL_TLS" will do the job for you like it should try to connect on all tls versions, so if your remote server only support TLSv1.2 even if your python is compiled with TLSv1.3 it will use TLSv1.2 and will connect as expected (Downgrading to TLS1.2 with no troubles).

You can check you have support of TLS version printing the booleans ...

import ssl
print(ssl.HAS_TLSv1)
print(ssl.HAS_TLSv1_1)
print(ssl.HAS_TLSv1_2)
print(ssl.HAS_TLSv1_3)

Hope this helps to demistify SSL/TLS is just a little process (to interchange security concerns between client and server) before any socket valuable data (like http requests) is send.

By the way SSL changed name to TLS and was hackable over the years and that's why we have so many versions (So internet is not as secure as you think)

🌐
CodeQL
codeql.github.com › codeql-query-help › python › py-insecure-default-protocol
Default version of SSL/TLS may be insecure — CodeQL query help documentation
The following code shows two different ways of setting up a connection using SSL or TLS. They are both potentially insecure because the default version is used. import ssl import socket # Using the deprecated ssl.wrap_socket method ssl.wrap_socket(socket.socket()) # Using SSLContext context = ssl.SSLContext()
🌐
GitHub
github.com › python › cpython › blob › main › Lib › ssl.py
cpython/Lib/ssl.py at main · python/cpython
"ssl.SSLContext() without protocol argument is deprecated.", category=DeprecationWarning, stacklevel=2 · ) protocol = PROTOCOL_TLS · self = _SSLContext.__new__(cls, protocol) return self ·
Author   python
🌐
7-Zip Documentation
documentation.help › Python-3.3 › ssl.html
18.2. ssl — TLS/SSL wrapper for socket objects - Python 3.3 Documentation
The SSL context created above will allow SSLv3 and TLSv1 connections, but not SSLv2. If you have advanced security requirements, fine-tuning of the ciphers enabled when negotiating a SSL session is possible through the SSLContext.set_ciphers() method. Starting from Python 3.2.3, the ssl module ...