Проблемы с SSL на Python - PullRequest
       7

Проблемы с SSL на Python

5 голосов
/ 05 октября 2019

У меня есть код на Python, который принимает (E) SMTP-запросы через aiosmtp, но так как я поместил этот код в Debian 10, у меня есть несколько ошибок, которых раньше не было (и мой код не изменился):

[SSL: NO_SHARED_CIPHER] нет общего шифра (_ssl.c: 1056)

Протокол сбоя при рукопожатии SSL: транспорт: <_SelectorSocketTransport fd = 11 чтение= запись с опросом =>

SSLError: [SSL: NO_SHARED_CIPHER] no shared cipher (_ssl.c:1056)
  File "asyncio/sslproto.py", line 625, in _on_handshake_complete
    raise handshake_exc
  File "asyncio/sslproto.py", line 189, in feed_ssldata
    self._sslobj.do_handshake()
  File "ssl.py", line 763, in do_handshake
    self._sslobj.do_handshake()

и:

[SSL: KRB5_S_INIT] данные приложения после уведомления о закрытии (_ssl.c: 2609)

Ошибка SSL в протоколе получения данных: транспорт: <_SelectorSocketTransport fd = 15 чтение = опрос записи =>

SSLError: [SSL: KRB5_S_INIT] application data after close notify (_ssl.c:2609)
  File "asyncio/sslproto.py", line 526, in data_received
    ssldata, appdata = self._sslpipe.feed_ssldata(data)
  File "asyncio/sslproto.py", line 207, in feed_ssldata
    self._sslobj.unwrap()
  File "ssl.py", line 767, in unwrap
    return self._sslobj.shutdown()

Я думаю, что эти две проблемы связаны.

К сожалению, эти две трассировки стека не показывают ничего, связанного с моим кодом, что затрудняет мне лучшее понимание того, где это происходит, и исключение не связано с другим исключением (Python3).


Вот версии моего пакета:

uname -a: Linux my-server 4.19.0-5-amd64 #1 SMP Debian 4.19.37-5+deb10u2 (2019-08-08) x86_64 GNU/Linux

python - версия: Python 3.7.3

pip freeze

aiomysql==0.0.20
aiosmtpd==1.2
asn1crypto==0.24.0
atpublic==1.0
authres==1.2.0
beanstalkc3==0.4.0
blinker==1.4
certifi==2018.8.24
cffi==1.12.3
chardet==3.0.4
Click==7.0
cloud-init==18.3
configobj==5.0.6
cryptography==2.6.1
distro-info==0.21
dkimpy==0.9.4
dnspython==1.16.0
fail2ban==0.10.2
Flask==1.1.1
idna==2.6
itsdangerous==1.1.0
Jinja2==2.10.1
jsonpatch==1.21
jsonpointer==1.10
jsonschema==2.6.0
MarkupSafe==1.1.0
mysqlclient==1.4.4
oauthlib==2.1.0
psutil==5.6.3
py3dns==3.2.1
pycparser==2.19
PyGObject==3.30.4
pyinotify==0.9.6
PyJWT==1.7.0
PyMySQL==0.9.2
PyNaCl==1.3.0
pyspf==2.0.13
pysrs==1.0.3
python-apt==1.8.4
python-dotenv==0.10.3
PyYAML==3.13
requests==2.21.0
sentry-sdk==0.12.3
six==1.12.0
systemd-python==234
unattended-upgrades==0.1
urllib3==1.24.1
uWSGI==2.0.18
Werkzeug==0.16.0

Я считаю, что если что-то не так с моим кодом,У меня была бы эта ошибка в Debian 9 и более ранних версиях, которой у меня никогда не было.

Я искал в SO и Google эту ошибку, но ничего не нашел. Я подозреваю некоторые проблемы в конкретной версии конкретного проекта (aiosmtpd, async или python), но не имею никакой подсказки.

Я надеюсь, что вы сможете мне помочь:)


Обновление:

Я добавил отслеживание шифров в сообщении. Общие шифры:

[[TLS_AES_256_GCM_SHA384, TLSv1.3, 256], [TLS_CHACHA20_POLY1305_SHA256, TLSv1.3, 256], [TLS_AES_128_GCM_SHA256, TLSv1.3, 128], [ECDHE-ECDSA-AES256-GCM-SHA384, TLSv1.2, 256], [ECDHE-RSA-AES256-GCM-SHA384, TLSv1.2, 256], [DHE-RSA-AES256-GCM-SHA384, TLSv1.2, 256], [ECDHE-ECDSA-CHACHA20-POLY1305, TLSv1.2, 256], [ECDHE-RSA-CHACHA20-POLY1305, TLSv1.2, 256], [DHE-RSA-CHACHA20-POLY1305, TLSv1.2, 256], [ECDHE-ECDSA-AES128-GCM-SHA256, TLSv1.2, 128], [ECDHE-RSA-AES128-GCM-SHA256, TLSv1.2, 128]]

И шифр для сокета: [ECDHE-RSA-AES256-GCM-SHA384, TLSv1.2, 256], который находится в общих шифрах.


Обновление 2

Iможет воспроизвести ошибку, но только при определенных условиях.

На новом сервере, вот код, который я запускаю:

import asyncio, logging, sys, signal, ssl
from aiosmtpd.controller import Controller
from aiosmtpd.handlers import Debugging
from aiosmtpd.smtp import SMTP

class ControllerTls(Controller):
    def factory(self):
        context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
        context.load_cert_chain('./certs/certificate.pem', './certs/id_rsa')
        context.load_dh_params('./certs/dhparams.pem')
        return SMTP(
            self.handler,
            tls_context=context
        )


# Temporary outputing errors from mail.log
streamHandler = logging.StreamHandler(sys.stdout)
streamHandler.setFormatter(logging.Formatter('[%(asctime)-15s] (%(levelname)s) - %(message)s'))
streamHandler.setLevel(logging.INFO)

maillog = logging.getLogger('mail.log')
maillog.setLevel(logging.INFO)
maillog.addHandler(streamHandler)

controller = ControllerTls(Debugging(), hostname='0.0.0.0', port=2125)
controller.start()
print('Controller started!')
sig = signal.sigwait([signal.SIGINT, signal.SIGQUIT])
controller.stop()

Это базовый сценарий, который помогает мне воспроизвести проблему.

На старом сервере я запускаю этот код:

import smtplib, ssl, sys

port = 25
if len(sys.argv) == 3:
    port = sys.argv[2]

def com(client, command, *args, **kwargs):
    result = getattr(client, command)(*args, **kwargs)
    if result[0] > 500:
        print('[FATAL] - An error occured!')
        print(result)
        client.quit()
        exit()

context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain('/var/www/towboat/certs/certificate.pem', '/var/www/towboat/certs/id_rsa')
context.set_ciphers('ECDHE-ECDSA-AES256-GCM-SHA384')
client = smtplib.SMTP(sys.argv[1], port=port)
com(client, 'ehlo')
com(client, 'starttls', context=context)
com(client, 'ehlo')
com(client, 'mail', 'contact@improvmx.com')
com(client, 'rcpt', 'cyril@improvmx.com')
com(client, 'quit')

print('All good !')

, которому я звоню:

sendmail.py {ip.of.new.server} 2125

На старом сервере (тот,запускает скрипт), я получаю эту ошибку:

Controller started!
[2019-10-08 15:57:11,878] (INFO) - Peer: ('ip.of.old.server', 45492)
[2019-10-08 15:57:11,878] (INFO) - ('ip.of.old.server', 45492) handling connection
[2019-10-08 15:57:11,880] (INFO) - ('ip.of.old.server', 45492) Data: b'ehlo {name old server}'
[2019-10-08 15:57:11,883] (INFO) - ('ip.of.old.server', 45492) Data: b'STARTTLS'
[2019-10-08 15:57:11,883] (INFO) - ('ip.of.old.server', 45492) STARTTLS
SSL handshake failed
protocol: <asyncio.sslproto.SSLProtocol object at 0x7f04d33d7d30>
transport: <_SelectorSocketTransport fd=7 read=polling write=<idle, bufsize=0>>
Traceback (most recent call last):
  File "/usr/lib/python3.7/asyncio/sslproto.py", line 625, in _on_handshake_complete
    raise handshake_exc
  File "/usr/lib/python3.7/asyncio/sslproto.py", line 189, in feed_ssldata
    self._sslobj.do_handshake()
  File "/usr/lib/python3.7/ssl.py", line 763, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: NO_SHARED_CIPHER] no shared cipher (_ssl.c:1056)
SSL error in data received
protocol: <asyncio.sslproto.SSLProtocol object at 0x7f04d33d7d30>
transport: <_SelectorSocketTransport closing fd=7 read=idle write=<idle, bufsize=0>>
Traceback (most recent call last):
  File "/usr/lib/python3.7/asyncio/sslproto.py", line 526, in data_received
    ssldata, appdata = self._sslpipe.feed_ssldata(data)
  File "/usr/lib/python3.7/asyncio/sslproto.py", line 189, in feed_ssldata
    self._sslobj.do_handshake()
  File "/usr/lib/python3.7/ssl.py", line 763, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: NO_SHARED_CIPHER] no shared cipher (_ssl.c:1056)
[2019-10-08 15:58:33,909] (INFO) - Connection lost during _handle_client()

Что странно, если я скопирую скрипт sendmail на свой локальный компьютер и запустлю его, указывая на новый сервер, у меня не будетошибка больше!

(Значит, проблема должна быть связана со старым сервером? Но почему новый сервер показывает исключение?!)

Если я переключаю сценарии (тестирую отправку электронного письма сновый сервер на старый), работает ...

Ответы [ 2 ]

2 голосов
/ 10 октября 2019

Я думаю, что это причина:

v1.1.1d на новом сервере, 1.1.0d на старом

1.1. Строка 1 представляет TLSv3 и многие другие весьма важные изменения - см. Журнал изменений .

Как я видел, вы открываете билет at aiosmtpd github Вы правильно догадались, что причиной возникновения ошибки является aiosmtpd. Причина в том, что он поддерживает Вам нужен как минимум Python 3.5 , который имеет no поддержку openssl 1.1.1 . Только python 3.7 (он не был полностью перенесен даже на python 3.6 ) в настоящее время поддерживает openssl 1.1.1 .

С последней версии aiosmtpd равно 1,2 (2018-09-01) Можно предположить (не видел PR (s) длячто) что они еще не внедрили новый openssl 1.1.1 [11 сентября 2018] , который вносит основных изменений.

ВашЕдинственный вариант, помимо предоставления PR для aiosmtpd , - это понижение вашего openssl до последней из 1.1.0 строки , которая в настоящее время 1.1. 0i .

1 голос
/ 14 октября 2019

Сначала я вижу, что шифр ECDHE-ECDSA-AES256-GCM-SHA384 не может работать, потому что обе стороны используют сертификаты RSA (мне интересно, действительно ли клиент использует свой сертификат для аутентификации или он только неправильно настроен в режиме сервера).
Если вы запускаете тот же клиентский скрипт на питоне, работающем с OpenSSL 1.1.1, серверы согласятся с TLSv1.3, где комплекты шифров больше не могут быть отключены, поэтому все еще разрешены даже с set_ciphers.
Я предполагаюесли вы выберете правильный ECDHE-RSA-AES256-GCM-SHA384 или вообще ничего не измените на своем "старом" (Openssl 1.1.0) Debian 9, он без проблем подключится с TLSv1.2 к новому серверу.

Тем не менее проблема со старым сценарием для определения наборов шифров на новом сервере также связана с тем фактом, что наборы шифров TLSv1.3 не могут быть отключены, и сценарий ожидает, что он может отключить любой набор шифров. для теста (именно так оно и работает).

Есть некоторые комплекты шифров, которые теперь полностью вышли из OpenSSLv1.1.1, но есть и другие, которые просто отключены по умолчанию (текущий Python разрешает только HIGH-шифры и по умолчанию MD5 / RC4 - и без шифров безпроверка подлинности - и, конечно, нет SSLv3 и более старых версий).
Начиная с python 3.6, получить список предлагаемых шифров очень просто (поэтому сломанный скрипт больше не нужен):

root@somehost:~# python3
Python 3.7.3 (default, Oct  7 2019, 12:56:13)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import ssl
>>> ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
>>> for cipher in ctx.get_ciphers(): print(cipher['name']+' '+cipher['protocol']) if cipher['auth'] == 'auth-rsa' else None
...
ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2
DHE-RSA-AES256-GCM-SHA384 TLSv1.2
ECDHE-RSA-CHACHA20-POLY1305 TLSv1.2
DHE-RSA-CHACHA20-POLY1305 TLSv1.2
ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2
DHE-RSA-AES128-GCM-SHA256 TLSv1.2
ECDHE-RSA-AES256-SHA384 TLSv1.2
DHE-RSA-AES256-SHA256 TLSv1.2
ECDHE-RSA-AES128-SHA256 TLSv1.2
DHE-RSA-AES128-SHA256 TLSv1.2
ECDHE-RSA-AES256-SHA TLSv1.0
DHE-RSA-AES256-SHA SSLv3
ECDHE-RSA-AES128-SHA TLSv1.0
DHE-RSA-AES128-SHA SSLv3
AES256-GCM-SHA384 TLSv1.2
AES128-GCM-SHA256 TLSv1.2
AES256-SHA256 TLSv1.2
AES128-SHA256 TLSv1.2
AES256-SHA SSLv3
AES128-SHA SSLv3

Для соединения TLSv1.2 минимальным требованием является AES128-SHA256 (без [EC] DHE KEX) или DHE-RSA-AES128-SHA256 / ECDHE-RSA-AES128-SHA256 / минимум OpenSSL Version 1.0.1 Released 14-March-2012. Так что не версия OpenSSL, поддерживающая TLSv1.2, должна быть не в состоянии подключиться.
Предполагается, что в работе могут быть некоторые клиенты OpenSSLv0.98 (по крайней мере, я знаю некоторых, которые мне еще предстоит поддерживать - хотя у меня есть все инструменты, необходимые длясборка производства против более новой OpenSSL). Они могут говорить только по TLSv1.0, если не по «запрещенному» SSLv3. Они могли бы по крайней мере использовать наборы, показанные как SSLv3.

Таким образом, не должно быть много клиентов, которые не могут использовать одно из предложений по умолчанию, но все же могут быть некоторые, которые более сконфигурированы для конкретных наборов шифров, которые не предпочтительны с сегодняшней точки зрения, или даже старшесистемы, менее мощное оборудование, другой поставщик библиотеки SSL, ... Итак, вам нужно найти пример, который на самом деле не работает, чтобы увидеть, какой дополнительный набор шифрования должен быть разрешен - или, возможно, другой механизм аутентификации.

Что касается другого момента в этом обсуждении, например, понижения OpenSSL, это определенно не тот путь. Не может быть много клиентов, которые не могут подключиться с настройками по умолчанию, и еще меньше, которые вообще не могут подключиться со всеми доступными настройками, которые OpenSSLv1.1.1 по-прежнему предлагает. И если им нужно обновление, определенно ..

Если вы действительно хотели / нуждались, вы можете скомпилировать более старый OpenSSL, чтобы перейти в другое место, и Python для этой старой версии OpenSSL. Может быть, запустить второй сервер на втором порту только для этих клиентов. Или Вы можете вместо этого запустить контейнер, но Вы не можете понизить версию system-OpenSSL.

Вторая ошибка, KRB5_S_INIT, действительно является ошибкой. Похоже на ошибку в модуле асинхронности ядра Python, который был представлен в Python 7.3. Но эта ошибка возникает только в том случае, если соединение в любом случае невозможно использовать (поэтому после того, как соединение было отменено из-за случая отсутствия общих наборов шифров).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...