From c124e2210b0f8bd4134f731c8b6478337e1a3328 Mon Sep 17 00:00:00 2001 From: Francois Gervais Date: Wed, 9 Dec 2020 12:12:54 -0500 Subject: [PATCH] Add basic SSL support This is a partial support as the CA certificate is not validated. This is so as this functionality is not implemented in all micropython ports. For example, it isn't currently supported in the esp32 port although a draft of the feature has been published: https://github.com/micropython/micropython/pull/5998 As the CA functionality is not there, we cannot use this parameter do decide when to switch to SSL mode as done in the CPython version. Instead we enable SSL when connecting to port 443 or 8443 which are well known ports used for TLS communications. One more thing is that this library relies on short reads to get data from the socket as it always ask for the max buffer length when reading. In micropython, the interface provided for SSL socket is only the one of a stream which doesn't allow short reads. To go around this we change the socket to non blocking which which has the side-effect of allowing short reads. However we then need to do manual polling and timeout on the socket which we do here. --- blynklib_mp.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/blynklib_mp.py b/blynklib_mp.py index d0d02d5..df133dc 100644 --- a/blynklib_mp.py +++ b/blynklib_mp.py @@ -5,6 +5,7 @@ __version__ = '0.2.6' import usocket as socket +import ussl as ssl import utime as time import ustruct as struct import uselect as select @@ -125,6 +126,7 @@ def internal_msg(self, *args): class Connection(Protocol): SOCK_MAX_TIMEOUT = const(5) SOCK_TIMEOUT = 0.05 + SOCK_SSL_TIMEOUT = const(1) EAGAIN = const(11) ETIMEDOUT = const(60) RETRIES_TX_DELAY = const(2) @@ -164,7 +166,11 @@ def send(self, data): try: retries -= 1 self._last_send_time = ticks_ms() - return self._socket.send(data) + try: + bytes_written = self._socket.send(data) + except AttributeError: + bytes_written = self._socket.write(data) + return bytes_written except (IOError, OSError): sleep_ms(self.RETRIES_TX_DELAY) @@ -172,7 +178,16 @@ def receive(self, length, timeout): d_buff = b'' try: self._set_socket_timeout(timeout) - d_buff += self._socket.recv(length) + try: + d_buff += self._socket.recv(length) + except AttributeError: + timeout = self.SOCK_SSL_TIMEOUT + while not d_buff and timeout > 0: + ret = self._socket.read(length) + if ret: + d_buff += ret + timeout -= self.SOCK_TIMEOUT + time.sleep(self.SOCK_TIMEOUT) if len(d_buff) >= length: d_buff = d_buff[:length] return d_buff @@ -203,6 +218,13 @@ def _get_socket(self): self._socket = socket.socket() self._socket.connect(socket.getaddrinfo(self.server, self.port)[0][-1]) self._set_socket_timeout(self.SOCK_TIMEOUT) + if self.port == 443 or self.port == 8443: + self.log('Using SSL socket...') + self._socket = ssl.wrap_socket(self._socket) + # Short reads are not supported in ssl mode. We work around + # this by setting the socket non blocking and doing manual + # polling/timeout. + self._socket.setblocking(False) self.log('Connected to server') except Exception as g_exc: raise BlynkError('Server connection failed: {}'.format(g_exc))