M-o-a-T / moat-mqtt

An async MQTT broker and client, plus DistKV integration
MIT License
21 stars 9 forks source link

Unable to establish MQTTS connections due to SSL error "unknown alert type" #5

Closed mikenerone closed 4 years ago

mikenerone commented 4 years ago

Any attempt to connect to a broker with an mqtts:// URI fails with a stack trace similar to:

Traceback (most recent call last):
  File "/Users/mikenerone/Library/Caches/pypoetry/virtualenvs/aiodxlclient-sf0sGL74-py3.8/lib/python3.8/site-packages/distmqtt/client.py", line 659, in _connect_coro
    await conn.start_tls()
  File "/Users/mikenerone/Library/Caches/pypoetry/virtualenvs/aiodxlclient-sf0sGL74-py3.8/lib/python3.8/site-packages/anyio/_networking.py", line 323, in start_tls
    await self._socket.start_tls(ssl_context, self._server_hostname,
  File "/Users/mikenerone/Library/Caches/pypoetry/virtualenvs/aiodxlclient-sf0sGL74-py3.8/lib/python3.8/site-packages/anyio/_networking.py", line 171, in start_tls
    self._raw_socket.do_handshake()
  File "/Users/mikenerone/.pyenv/versions/3.8.5/lib/python3.8/ssl.py", line 1309, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: UNKNOWN_ALERT_TYPE] unknown alert type (_ssl.c:1123)

The cause is that SSL negotiation is initiated twice in MQTTClient._connect_coro, once during anyio.connect_tcp() (L655) because autostart_tls=True is in **kwargs, and then again explicitly via conn.start_tls() at L659. I'll have a fix PR shortly (or perhaps tomorrow if I get too sleepy :D).

mikenerone commented 4 years ago

On a related note, this test always passes:

    def test_connect_tcp_secure(self):
        async def test_coro():
            async with open_mqttclient(config={"check_hostname": False}) as client:
                ca = os.path.join(os.path.dirname(os.path.realpath(__file__)), "mosquitto.org.crt")
                await client.connect("mqtts://test.mosquitto.org/", cafile=ca)
                self.assertIsNotNone(client.session)

        try:
            anyio.run(test_coro)
        except ConnectException:
            log.error("Broken by server")

It always passes because at client.py:703, _connect_coro() catches the SSL failures and raises a ConnectException instead, which the test then ignores (only logging it). In fact, it passes even thought the SSL validation actually fails currently (I suspect the CA file is outdated, but I didn't dig into it).

I'm not sure how you'd want to fix this one within the bowels of _connect_coro(), so I won't try, but if fixed it would have caught this situation. A few of the other tests have the same problem, as well.