首页 > 解决方案 > Python 上的 SSL 问题

问题描述

我在 Python 中有一个通过 aiosmtp 接受 (E)SMTP 请求的代码,但是由于我在 Debian 10 上推送了此代码,因此我遇到了一些以前不存在的错误(而且我的代码没有改变):

[SSL: NO_SHARED_CIPHER] 没有共享密码 (_ssl.c:1056)

SSL 握手失败协议:传输:<_SelectorSocketTransport fd=11 read=polling write=>

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 read=polling write=>

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)无关。


这是我的包的版本:

unname -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 3.7.3

点冻结

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

我可以重现错误,但只能在特定条件下。

在新服务器上,这是我运行的代码:

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 脚本,然后将其指向新服务器运行,我就不再有错误了!

(所以问题肯定和老服务器有关?但是为什么新服务器显示异常?!)

如果我切换脚本(测试从新服务器向旧服务器发送电子邮件),它可以工作......

标签: pythonpython-3.xssl

解决方案


我认为这是原因:

新服务器上的 v1.1.1d,旧服务器上的 1.1.0d

1.1.1行介绍了TLSv3和许多其他非常重要的更改 -请参阅更改日志

正如我看到您在aiosmtpd github 上打开票证一样,您已经正确猜到您收到错误的原因是 aiosmtpd。原因是它支持您至少需要 Python 3.5,它支持openssl 1.1.1。目前只有python 3.7(甚至还没有完全向后移植到python 3.6)支持openssl 1.1.1

由于 aiosmtpd 的最新版本1.2 (2018-09-01),因此可以假设(没有看到任何PR)他们尚未实施新的 openssl 1.1.1 [2018 年 9 月 11 日]是引入了重大变化。

除了为aiosmtpd提供 PR之外,您唯一的选择是将您的 openssl降级到1.1.0 行的最新版本,该行目前是1.1.0i


推荐阅读