I want to connect with a service whose owner told me I need to connect with TLS 1.2.
The problem is that my Python uses TLS 1.3
I checked it with this command python -c "import requests; print(requests.get('https://www.howsmyssl.com/a/check', verify=False).json()['tls_version'])"
Is it possible to downgrade TLS to 1.2?
Well,
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.
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/emailAddress=joseveland@gmail.com" 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
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
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)