SSL 1.3 maybe

This commit is contained in:
emdee 2022-11-03 02:51:14 +00:00
parent 3e093a1a41
commit 5ff9bd0680
2 changed files with 255 additions and 39 deletions

View file

@ -13,6 +13,7 @@ import traceback
from time import sleep from time import sleep
from threading import Thread from threading import Thread
from random import shuffle from random import shuffle
from errno import errorcode
from OpenSSL import SSL from OpenSSL import SSL
import warnings import warnings
@ -38,15 +39,42 @@ import wrapper.toxencryptsave as tox_encrypt_save
global LOG global LOG
LOG = logging.getLogger('app.'+'ts') LOG = logging.getLogger('app.'+'ts')
class SyniToxError(Exception): pass class SyniToxError(BaseException): pass
NAME = 'SyniTox' NAME = 'SyniTox'
SSL_TOR_RANGE = '172.'
# possible CA locations picks the first one # possible CA locations picks the first one
lCAs = ['/etc/ssl/cacert.pem', lCAs = [# debian and gentoo
# debian '/etc/ssl/certs/',
'/etc/ssl/certs/'] ]
lCAfs = SSL._CERTIFICATE_FILE_LOCATIONS
# openssl ciphers -s -v|grep 1.3 > /tmp/v1.3
lOPENSSL_13_CIPHERS = ['TLS_AES_256_GCM_SHA384',
'TLS_CHACHA20_POLY1305_SHA256',
'TLS_AES_128_GCM_SHA256']
lOPENSSL_12_CIPHERS = ['ECDHE-ECDSA-AES256-GCM-SHA384',
'ECDHE-RSA-AES256-GCM-SHA384',
'DHE-RSA-AES256-GCM-SHA384',
'ECDHE-ECDSA-CHACHA20-POLY1305',
'ECDHE-RSA-CHACHA20-POLY1305',
'DHE-RSA-CHACHA20-POLY1305',
'ECDHE-ECDSA-AES128-GCM-SHA256',
'ECDHE-RSA-AES128-GCM-SHA256',
'DHE-RSA-AES128-GCM-SHA256',
'ECDHE-ECDSA-AES256-SHA384',
'ECDHE-RSA-AES256-SHA384',
'DHE-RSA-AES256-SHA256',
'ECDHE-ECDSA-AES128-SHA256',
'ECDHE-RSA-AES128-SHA256',
'DHE-RSA-AES128-SHA256',
'AES256-GCM-SHA384',
'AES128-GCM-SHA256',
'AES256-SHA256',
'AES128-SHA256'
]
bot_toxname = 'SyniTox' bot_toxname = 'SyniTox'
iSocks5ErrorMax = 5
iSocks5Error = 0
# tox.py can be called by callbacks # tox.py can be called by callbacks
def LOG_ERROR(a): print('EROR> '+a) def LOG_ERROR(a): print('EROR> '+a)
@ -62,8 +90,8 @@ def LOG_TRACE(a):
if bVERBOSE: print('TRAC> '+a) if bVERBOSE: print('TRAC> '+a)
# https://wiki.python.org/moin/SSL # https://wiki.python.org/moin/SSL
def ssl_verify_cb(HOST, override=False): def ssl_verify_cb(host, override=False):
assert HOST assert host
# wrapps host # wrapps host
def ssl_verify(*args): def ssl_verify(*args):
""" """
@ -71,28 +99,33 @@ def ssl_verify_cb(HOST, override=False):
should return true if verification passes and false otherwise should return true if verification passes and false otherwise
""" """
LOG.debug(f"ssl_verify {len(args)} {args}") LOG.debug(f"ssl_verify {len(args)} {args}")
# app.ts WARNING SSL error: ([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],) # on .onion - fair enough
if override: return True if override: return True
ssl_conn, x509, error_num, depth, return_code = args ssl_conn, x509, error_num, depth, return_code = args
if error_num != 0: if error_num != 0:
LOG.warn(f"ssl_verify error_num={error_num} {errorcode.get(error_num)}")
return False return False
if depth != 0: if depth != 0:
# don't validate names of root certificates # don't validate names of root certificates
return True return True
if x509.get_subject().commonName == HOST: if x509.get_subject().commonName == host:
return True return True
LOG.warn(f"ssl_verify {x509.get_subject().commonName} {HOST}")
# allow matching subdomains # allow matching subdomains
have , want = x509.get_subject().commonName, HOST have , want = x509.get_subject().commonName, host
if len(have.split('.')) == len(want.split('.')) and len(want.split('.')) > 2: if len(have.split('.')) == len(want.split('.')) and len(want.split('.')) > 2:
if have.split('.')[1:] == want.split('.')[1:]: if have.split('.')[1:] == want.split('.')[1:]:
LOG.warn(f"ssl_verify accepting {x509.get_subject().commonName} for {host}")
return True return True
return False return False
return ssl_verify return ssl_verify
class SyniTox(Tox): class SyniTox(Tox):
def __init__(self, def __init__(self,
@ -183,38 +216,61 @@ class SyniTox(Tox):
def start_ssl(self, HOST): def start_ssl(self, HOST):
if not self._ssl_context: if not self._ssl_context:
if HOST.endswith('.onion'): try:
OP_NO_TLSv1_3 = SSL._lib.SSL_OP_NO_TLSv1_3
except AttributeError:
if self._oArgs.irc_ssl == 'tlsv1.3':
LOG.warning("SSL._lib.SSL_OP_NO_TLSv1_3 is not supported")
LOG.warning("Downgrading SSL to tlsv1.2 ")
self._oArgs.irc_ssl = 'tlsv1.2'
else:
LOG.debug("SSL._lib.SSL_OP_NO_TLSv1_3 is not supported")
else:
LOG.debug("SSL._lib.SSL_OP_NO_TLSv1_3 is supported")
if self._oArgs.irc_connect.endswith('.onion') or \
self._oArgs.irc_connect.startswith(SSL_TOR_RANGE):
override = True override = True
else: else:
override = False override = False
# TLSv1_3_METHOD does not exist # TLSv1_3_METHOD does not exist
context = SSL.Context(SSL.TLSv1_2_METHOD) context = SSL.Context(SSL.TLSv1_2_METHOD)
# SSL.OP_NO_TLSv1_1 is allowed
context.set_options(SSL.OP_NO_SSLv2|SSL.OP_NO_SSLv3|SSL.OP_NO_TLSv1) context.set_options(SSL.OP_NO_SSLv2|SSL.OP_NO_SSLv3|SSL.OP_NO_TLSv1)
# this maybe necessary even for a 1.3 site to get the handshake
# in pyOpenSSL - or was it a protocol downgrade attack?
#? context.set_cipher_list("DEFAULT:SECLEVEL=1")
# im getting SSL error: ([('SSL routines', 'tls_construct_client_hello', 'no protocols available')],)
# if I use tlsv1.3 or tlsv1.2 without this on a tlsv1.3 capacble site
if self._oArgs.irc_pem: if self._oArgs.irc_pem:
key = self._oArgs.irc_pem key = self._oArgs.irc_pem
assert os.path.exists(key), key assert os.path.exists(key), key
val = SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT val = SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT
LOG.info('Using keyfile: %s' % self._oArgs.irc_pem) LOG.info('Using keyfile: %s' % self._oArgs.irc_pem)
if False: if True:
key = self._oArgs.irc_pem.replace('.pem', '.crt')
context.use_certificate_file(key, filetype=SSL.FILETYPE_PEM) context.use_certificate_file(key, filetype=SSL.FILETYPE_PEM)
if True: if True:
# key = self._oArgs.irc_pem.replace('.pem', '.key') key = self._oArgs.irc_pem.replace('.pem', '.key')
assert os.path.exists(key), key assert os.path.exists(key), key
context.use_privatekey_file(key, filetype=SSL.FILETYPE_PEM) context.use_privatekey_file(key, filetype=SSL.FILETYPE_PEM)
else: else:
val = SSL.VERIFY_PEER val = SSL.VERIFY_PEER
context.set_verify(val, ssl_verify_cb(HOST, override)) context.set_verify(val, ssl_verify_cb(HOST, override))
assert os.path.exists(self._oArgs.irc_ca), self._oArgs.irc_ca if self._oArgs.irc_cafile:
if os.path.isdir(self._oArgs.irc_ca): # context.load_verify_locations(capath=self._oArgs.irc_ca)
context.load_verify_locations(capath=self._oArgs.irc_ca) context.load_verify_locations(self._oArgs.irc_cafile, capath=self._oArgs.irc_cadir)
else: elif self._oArgs.irc_cadir:
context.load_verify_locations(cafile=self._oArgs.irc_ca) context.load_verify_locations(None, capath=self._oArgs.irc_cadir)
if False: if self._oArgs.irc_ssl == 'tlsv1.1':
pass context.set_min_proto_version(SSL.TLS1_1_VERSION)
elif self._oArgs.irc_ssl == 'tls1.2': elif self._oArgs.irc_ssl == 'tlsv1.2':
context.set_cipher_list(bytes(' '.join(lOPENSSL_12_CIPHERS), 'UTF-8'))
context.set_min_proto_version(SSL.TLS1_2_VERSION) context.set_min_proto_version(SSL.TLS1_2_VERSION)
elif self._oArgs.irc_ssl == 'tls1.3': elif self._oArgs.irc_ssl == 'tlsv1.3':
#? context.set_cipher_list(bytes(' '.join(lOPENSSL_13_CIPHERS), 'UTF-8'))
context.set_min_proto_version(SSL.TLS1_3_VERSION) context.set_min_proto_version(SSL.TLS1_3_VERSION)
self._ssl_context = context self._ssl_context = context
@ -432,13 +488,35 @@ class SyniTox(Tox):
self.callback_friend_request(None) self.callback_friend_request(None)
self.callback_friend_request(None) self.callback_friend_request(None)
def diagnose_ciphers(self, irc):
cipher_name = irc.get_cipher_name()
LOG.info(f"cipher_name={irc.get_cipher_name()}")
LOG.debug(f"get_cipher_list={irc.get_cipher_list()}")
cipher_list=irc.get_cipher_list()
for ci in lOPENSSL_13_CIPHERS:
if ci in cipher_list: LOG.info(f"server supports v1.3 cipher {ci}")
cipher_name = irc.get_cipher_name()
LOG.info(f"cipher_name={irc.get_cipher_name()}")
if self._oArgs.irc_ssl == 'tlsv1.2':
assert cipher_name in lOPENSSL_12_CIPHERS, cipher_name
elif self._oArgs.irc_ssl == 'tlsv1.3':
assert cipher_name in lOPENSSL_13_CIPHERS, cipher_name
for cert in irc.get_peer_cert_chain():
# x509 objects - just want the /CN
LOG.debug(f"{cert.get_subject()} {cert.get_issuer()}")
assert irc.get_protocol_version_name().lower() == \
self._oArgs.irc_ssl, \
irc.get_protocol_version_name().lower()
def irc_init(self): def irc_init(self):
global iSocks5Error
if not self.bRouted(): return if not self.bRouted(): return
nick = self._oArgs.irc_nick nick = self._oArgs.irc_nick
realname = self._oArgs.irc_name realname = self._oArgs.irc_name
ident = self._oArgs.irc_ident ident = self._oArgs.irc_ident
LOG.info(f"irc_init proxy={self._oArgs.proxy_type} SSL={self._oArgs.irc_ssl}")
LOG.info(f"irc_init proxy={self._oArgs.proxy_type}")
try: try:
if self._oArgs.proxy_type == 2: if self._oArgs.proxy_type == 2:
socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5,
@ -474,27 +552,37 @@ class SyniTox(Tox):
irc.set_tlsext_host_name(None) irc.set_tlsext_host_name(None)
else: else:
irc.set_tlsext_host_name(bytes(self._oArgs.irc_host, 'UTF-8')) irc.set_tlsext_host_name(bytes(self._oArgs.irc_host, 'UTF-8'))
irc.set_connect_state() #? irc.set_connect_state()
while True: while True:
try: try:
irc.do_handshake() irc.do_handshake()
except SSl.WantReadError: except SSL.WantReadError:
rd,_,_ = select.select([irc], [], [], irc.gettimeout()) rd,_,_ = select.select([irc], [], [], irc.gettimeout())
if not rd: if not rd:
raise socket.timeout('timeout') raise socket.timeout('timeout')
continue continue
except SSl.Error as e: except SSL.Error as e:
raise raise
break break
for cert in irc.get_peer_cert_chain(): self.diagnose_ciphers(irc)
print(f"{cert.get_subject} {cert.get_issuer}")
else: else:
irc.connect((ip, self._oArgs.irc_port)) irc.connect((ip, self._oArgs.irc_port))
LOG.info(f"IRC {'SSL ' if self._oArgs.irc_ssl else ''} connected ") LOG.info(f"IRC {'SSL ' if self._oArgs.irc_ssl else ''} connected ")
except wrapper_tests.socks.Socks5Error as e: except wrapper_tests.socks.Socks5Error as e:
if len(e.args[0]) == 2 and e.args[0][0] ==2: iSocks5Error += 1
if iSocks5Error >= iSocks5ErrorMax:
raise SyniToxError(f"{e.args}")
if len(e.args[0]) == 2 and e.args[0][0] == 2:
LOG.warn(f"Socks5Error: do you have Tor SafeSocks set? {e.args[0]}") LOG.warn(f"Socks5Error: do you have Tor SafeSocks set? {e.args[0]}")
elif len(e.args[0]) == 2 and e.args[0][0] == 5:
# (5, 'Connection refused')
LOG.warn(f"Socks5Error: do you have Tor running? {e.args[0]}")
raise SyniToxError(f"{e.args}")
elif len(e.args[0]) == 2 and e.args[0][0] in [1, 6]:
# (6, 'TTL expired'), 1, ('general SOCKS server failure')
# Missing mapping for virtual address '172.17.140.117'. Refusing.
LOG.warn(f"Socks5Error: {e.args[0]}")
return return
else: else:
LOG.error(f"Socks5Error: {e.args}") LOG.error(f"Socks5Error: {e.args}")
@ -502,16 +590,18 @@ class SyniTox(Tox):
except socket.timeout as e: except socket.timeout as e:
LOG.warn(f"socket error: {e.args}") LOG.warn(f"socket error: {e.args}")
return return
except ( ConnectionRefusedError) as e:
raise SyniToxError(f"{e.args}")
except ( SSL.Error, ) as e: except ( SSL.Error, ) as e:
iSocks5Error += 1
if iSocks5Error >= iSocks5ErrorMax:
raise SyniToxError(f"{e.args}")
LOG.warn(f"SSL error: {e.args}") LOG.warn(f"SSL error: {e.args}")
return return
except (SSL.SysCallError, ) as e: except (SSL.SysCallError, ) as e:
LOG.warn(f"SSLSyscall error: {e.args}") LOG.warn(f"SSLSyscall error: {e.args}")
LOG.warn(traceback.format_exc()) LOG.warn(traceback.format_exc())
return return
except wrapper_tests.socks.Socks5Error as e:
# (2, 'connection not allowed by ruleset')
raise
except Exception as e: except Exception as e:
LOG.warn(f"Error: {e}") LOG.warn(f"Error: {e}")
LOG.warn(traceback.format_exc()) LOG.warn(traceback.format_exc())
@ -524,6 +614,7 @@ class SyniTox(Tox):
self._oArgs.irc_ident, self._oArgs.irc_ident,
self._oArgs.irc_host, self._oArgs.irc_host,
self._oArgs.irc_name), 'UTF-8')) self._oArgs.irc_name), 'UTF-8'))
# OSError: [Errno 9] Bad file descriptor
def dht_init(self): def dht_init(self):
if not self.bRouted(): return if not self.bRouted(): return
@ -673,7 +764,11 @@ class SyniTox(Tox):
else: else:
LOG.error("you must provide a password to register") LOG.error("you must provide a password to register")
raise RuntimeError("you must provide a password to register") raise RuntimeError("you must provide a password to register")
try:
self.irc.send(bytes('JOIN %s\r\n' % self._oArgs.irc_chan, 'UTF-8')) self.irc.send(bytes('JOIN %s\r\n' % self._oArgs.irc_chan, 'UTF-8'))
except BrokenPipeError:
raise SyniToxError('BrokenPipeError')
# put off init_groups until you have joined IRC # put off init_groups until you have joined IRC
self.init_groups() self.init_groups()
# Make sure we are in # Make sure we are in
@ -1013,7 +1108,10 @@ def oArgparse(lArgv):
for elt in lCAs: for elt in lCAs:
if os.path.exists(elt): if os.path.exists(elt):
CAcs.append(elt) CAcs.append(elt)
break CAfs = []
for elt in lCAfs:
if os.path.exists(elt):
CAfs.append(elt)
parser.add_argument('--log_level', type=int, default=10) parser.add_argument('--log_level', type=int, default=10)
parser.add_argument('--bot_name', type=str, default=bot_toxname) parser.add_argument('--bot_name', type=str, default=bot_toxname)
@ -1038,9 +1136,12 @@ def oArgparse(lArgv):
# #
parser.add_argument('--irc_ssl', type=str, default='', parser.add_argument('--irc_ssl', type=str, default='',
help="TLS version; empty is no SSL", help="TLS version; empty is no SSL",
choices=['', 'tls1.2', 'tls1.3']) choices=['', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3'])
parser.add_argument('--irc_ca', type=str, parser.add_argument('--irc_cafile', type=str,
help="Certificate Authority file or directory", help="Certificate Authority file",
default=CAfs[0])
parser.add_argument('--irc_cadir', type=str,
help="Certificate Authority directory",
default=CAcs[0]) default=CAcs[0])
parser.add_argument('--irc_pem', type=str, default='', parser.add_argument('--irc_pem', type=str, default='',
help="Certificate and key as pem; use openssl req -x509 -nodes -newkey rsa:2048") help="Certificate and key as pem; use openssl req -x509 -nodes -newkey rsa:2048")
@ -1100,6 +1201,10 @@ def main(lArgs=None):
assert oTOX_OARGS.irc_host or oTOX_OARGS.irc_connect assert oTOX_OARGS.irc_host or oTOX_OARGS.irc_connect
if not oTOX_OARGS.irc_connect: if not oTOX_OARGS.irc_connect:
oTOX_OARGS.irc_connect = oTOX_OARGS.irc_host oTOX_OARGS.irc_connect = oTOX_OARGS.irc_host
if oTOX_OARGS.irc_cadir:
assert os.path.isdir(oTOX_OARGS.irc_cadir)
if oTOX_OARGS.irc_cafile:
assert os.path.isfile(oTOX_OARGS.irc_cafile)
global oTOX_OPTIONS global oTOX_OPTIONS
oTOX_OPTIONS = oToxygenToxOptions(oTOX_OARGS) oTOX_OPTIONS = oToxygenToxOptions(oTOX_OARGS)
ts.vSetupLogging(oTOX_OARGS) ts.vSetupLogging(oTOX_OARGS)

111
tox-irc-sync_test.bash Normal file
View file

@ -0,0 +1,111 @@
#!/bin/bash
#export LD_LIBRARY_PATH=/usr/local/lib
#export TOXCORE_LIBS=/mnt/linuxPen19/var/local/src/c-toxcore/_build
export TOXCORE_LIBS=/mnt/o/var/local/src/tox_profile/libs
export PYTHONPATH=/mnt/o/var/local/src/toxygen_wrapper.git/
[ -f /usr/local/bin/usr_local_tput.bash ] && \
. /usr/local/bin/usr_local_tput.bash || {
DBUG() { echo DEBUG $* ; }
INFO() { echo INFO $* ; }
WARN() { echo WARN $* ; }
ERROR() { echo ERROR $* ; }
}
TLS=2
a=`openssl ciphers -s -v|grep -c v1.3`
if [ "$a" -lt 3 ] ; then
WARN no SSSL TLSv1.3 ciphers available to the client.
TLS=2
fi
declare -a RARGS
RARGS=(
--log_level 10
)
[ -n "$socks_proxy" ] && \
RARGS+=(
--proxy_type 2
--proxy_port 9050
--proxy_host 127.0.0.1
)
declare -a LARGS
LARGS=(
--irc_host irc.oftc.net
--irc_port 7000
--irc_ssl ""
--irc_ident SyniTox
--irc_name SyniTox
--irc_nick SyniTox
--irc_pass password
)
DBUG $?
if [ $# -eq 0 -o "$1" = 1 ] ; then
INFO No SSL
python3 tox-irc-sync.py "${LARGS[@]}" "${RARGS[@]}" "$@"
DBUG $?
fi
CIPHER_DOWNGRADE_OVER_TOR="
Nmap scan report for irc.oftc.net (130.239.18.116)
Host is up (0.26s latency).
Other addresses for irc.oftc.net (not scanned): (null)
rDNS record for 130.239.18.116: solenoid.acc.umu.se
PORT STATE SERVICE
6697/tcp open ircs-u
| ssl-enum-ciphers:
| TLSv1.0:
| ciphers:
| TLS_DHE_RSA_WITH_AES_128_CBC_SHA (dh 2048) - A
| compressors:
| cipher preference: indeterminate
| cipher preference error: Too few ciphers supported
|_ least strength: A
"
# I know that site does v1.3 3 ciphers
if [ $# -eq 0 -o "$1" = 2 ] ; then
nmap --script ssl-enum-ciphers --proxies socks4://127.0.0.1:9050 -p 6697 irc.oftc.net
# oftcnet6xg6roj6d7id4y4cu6dchysacqj2ldgea73qzdagufflqxrid.onion
# irc.oftc.net
LARGS=(
--irc_host irc.oftc.net
--irc_port 6697
--irc_ssl tlsv1.$TLS
--irc_ident SyniTox
--irc_name SyniTox
--irc_nick SyniTox
--irc_pass password
--irc_pem $HOME/.config/ssl/irc.oftc.net/SyniTox.pem
# E178E7B9BD9E540278118193AD2C84DEF9B35E85
--irc_fp $HOME/.config/ssl/irc.oftc.net/SyniTox.fp
--irc_cadir '/etc/ssl/certs'
--irc_cafile /etc/ssl/cacert.pem
)
DBUG $?
fi
if [ $# -eq 0 -o "$1" = 2 ] ; then
INFO SSL
python3 tox-irc-sync.py "${LARGS[@]}" "${RARGS[@]}" "$@"
fi
ip=oftcnet6xg6roj6d7id4y4cu6dchysacqj2ldgea73qzdagufflqxrid.onion
if [ $# -eq 0 -o "$1" = 3 ] ; then
nmap --script ssl-enum-ciphers --proxies socks4://127.0.0.1:9050 -p 6697 $ip
INFO Onion
python3 tox-irc-sync.py "${LARGS[@]}" --irc_connect $ip "${RARGS[@]}" "$@"
DBUG $?
fi
ip=`tor-resolve -4 $ip`
if [ $? -eq 0 -a -n "$ip" ] && [ $# -eq 0 -o "$1" = 4 ] ; then
nmap --script ssl-enum-ciphers --proxies socks4://127.0.0.1:9050 -p 6697 $ip
INFO IP $ip
python3 tox-irc-sync.py "${LARGS[@]}" --irc_connect $ip "${RARGS[@]}" "$@"
DBUG $?
fi