Thanks to visit codestin.com
Credit goes to github.com

Skip to content

ssl: restructure micropython interface in a tls module #793

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion micropython/bundles/bundle-networking/manifest.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
metadata(
version="0.1.0",
version="0.2.0",
description="Common networking packages for all network-capable deployments of MicroPython.",
)

require("mip")
require("ntptime")
require("ssl")
require("requests")
require("webrepl")

Expand Down
2 changes: 1 addition & 1 deletion micropython/umqtt.simple/manifest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
metadata(description="Lightweight MQTT client for MicroPython.", version="1.3.4")
metadata(description="Lightweight MQTT client for MicroPython.", version="1.4.0")

# Originally written by Paul Sokolovsky.

Expand Down
12 changes: 4 additions & 8 deletions micropython/umqtt.simple/umqtt/simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ def __init__(
user=None,
password=None,
keepalive=0,
ssl=False,
ssl_params={},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems now like a "breaking" change, one needs to create and pass a SSLContext, see e.g. https://github.com/orgs/micropython/discussions/13624, I think this should be something like

if self.ssl:
             if hasattr(self.ssl, "wrap_socket"):
                 self.sock = self.ssl.wrap_socket(self.sock, **self.ssl_params)
             else:
                 import ssl as _ssl

                 self.sock = _ssl.wrap_socket(self.sock, **self.ssl_params)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was not aware that breaking changes here are not possible. I would argue that this breaking change is good here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this breaking change is a fair change to make. We need to improve and make progress on things and sometimes it's not practical to retain backwards compatibility on everything.

In this case using the new library with old user code will raise an exception if the ssl argument is used. So it's easy for the user to know that things need to be changed.

And the new scheme matches better how SSL works in asyncio, passing in an SSLContext.

In the end a user can just keep using an old version of umqtt if needed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then document this somewhere in the umqtt's README or next release info should be fine 👍🏼

ssl=None,
):
if port == 0:
port = 8883 if ssl else 1883
Expand All @@ -26,7 +25,6 @@ def __init__(
self.server = server
self.port = port
self.ssl = ssl
self.ssl_params = ssl_params
self.pid = 0
self.cb = None
self.user = user
Expand Down Expand Up @@ -67,15 +65,13 @@ def connect(self, clean_session=True):
addr = socket.getaddrinfo(self.server, self.port)[0][-1]
self.sock.connect(addr)
if self.ssl:
import ussl

self.sock = ussl.wrap_socket(self.sock, **self.ssl_params)
self.sock = self.ssl.wrap_socket(self.sock, server_hostname=self.server)
premsg = bytearray(b"\x10\0\0\0\0\0")
msg = bytearray(b"\x04MQTT\x04\x02\0\0")

sz = 10 + 2 + len(self.client_id)
msg[6] = clean_session << 1
if self.user is not None:
if self.user:
sz += 2 + len(self.user) + 2 + len(self.pswd)
msg[6] |= 0xC0
if self.keepalive:
Expand All @@ -101,7 +97,7 @@ def connect(self, clean_session=True):
if self.lw_topic:
self._send_str(self.lw_topic)
self._send_str(self.lw_msg)
if self.user is not None:
if self.user:
self._send_str(self.user)
self._send_str(self.pswd)
resp = self.sock.read(4)
Expand Down
2 changes: 1 addition & 1 deletion micropython/urllib.urequest/manifest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
metadata(version="0.6.0")
metadata(version="0.7.0")

# Originally written by Paul Sokolovsky.

Expand Down
6 changes: 4 additions & 2 deletions micropython/urllib.urequest/urllib/urequest.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def urlopen(url, data=None, method="GET"):
if proto == "http:":
port = 80
elif proto == "https:":
import ussl
import tls

port = 443
else:
Expand All @@ -29,7 +29,9 @@ def urlopen(url, data=None, method="GET"):
try:
s.connect(ai[-1])
if proto == "https:":
s = ussl.wrap_socket(s, server_hostname=host)
context = tls.SSLContext(tls.PROTOCOL_TLS_CLIENT)
context.verify_mode = tls.CERT_NONE
s = context.wrap_socket(s, server_hostname=host)

s.write(method)
s.write(b" /")
Expand Down
2 changes: 1 addition & 1 deletion python-ecosys/requests/manifest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
metadata(version="0.8.1", pypi="requests")
metadata(version="0.9.0", pypi="requests")

package("requests")
6 changes: 4 additions & 2 deletions python-ecosys/requests/requests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def request(
if proto == "http:":
port = 80
elif proto == "https:":
import ussl
import tls

port = 443
else:
Expand All @@ -90,7 +90,9 @@ def request(
try:
s.connect(ai[-1])
if proto == "https:":
s = ussl.wrap_socket(s, server_hostname=host)
context = tls.SSLContext(tls.PROTOCOL_TLS_CLIENT)
context.verify_mode = tls.CERT_NONE
s = context.wrap_socket(s, server_hostname=host)
s.write(b"%s /%s HTTP/1.0\r\n" % (method, path))
if "Host" not in headers:
s.write(b"Host: %s\r\n" % host)
Expand Down
4 changes: 2 additions & 2 deletions python-stdlib/ssl/manifest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
metadata(version="0.1.0")
metadata(version="0.2.0")

module("ssl.py")
module("ssl.py", opt=3)
92 changes: 64 additions & 28 deletions python-stdlib/ssl/ssl.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,72 @@
from ussl import *
import ussl as _ussl
import tls
from tls import (
CERT_NONE,
CERT_OPTIONAL,
CERT_REQUIRED,
MBEDTLS_VERSION,
PROTOCOL_TLS_CLIENT,
PROTOCOL_TLS_SERVER,
)

# Constants
for sym in "CERT_NONE", "CERT_OPTIONAL", "CERT_REQUIRED":
if sym not in globals():
globals()[sym] = object()

class SSLContext:
def __init__(self, *args):
self._context = tls.SSLContext(*args)
self._context.verify_mode = CERT_NONE

@property
def verify_mode(self):
return self._context.verify_mode

@verify_mode.setter
def verify_mode(self, val):
self._context.verify_mode = val

def load_cert_chain(self, certfile, keyfile):
if isinstance(certfile, str):
with open(certfile, "rb") as f:
certfile = f.read()
if isinstance(keyfile, str):
with open(keyfile, "rb") as f:
keyfile = f.read()
self._context.load_cert_chain(certfile, keyfile)

def load_verify_locations(self, cafile=None, cadata=None):
if cafile:
with open(cafile, "rb") as f:
cadata = f.read()
self._context.load_verify_locations(cadata)

def wrap_socket(
self, sock, server_side=False, do_handshake_on_connect=True, server_hostname=None
):
return self._context.wrap_socket(
sock,
server_side=server_side,
do_handshake_on_connect=do_handshake_on_connect,
server_hostname=server_hostname,
)


def wrap_socket(
sock,
keyfile=None,
certfile=None,
server_side=False,
key=None,
cert=None,
cert_reqs=CERT_NONE,
*,
ca_certs=None,
server_hostname=None
cadata=None,
server_hostname=None,
do_handshake=True,
):
# TODO: More arguments accepted by CPython could also be handled here.
# That would allow us to accept ca_certs as a positional argument, which
# we should.
kw = {}
if keyfile is not None:
kw["keyfile"] = keyfile
if certfile is not None:
kw["certfile"] = certfile
if server_side is not False:
kw["server_side"] = server_side
if cert_reqs is not CERT_NONE:
kw["cert_reqs"] = cert_reqs
if ca_certs is not None:
kw["ca_certs"] = ca_certs
if server_hostname is not None:
kw["server_hostname"] = server_hostname
return _ussl.wrap_socket(sock, **kw)
con = SSLContext(PROTOCOL_TLS_SERVER if server_side else PROTOCOL_TLS_CLIENT)
if cert or key:
con.load_cert_chain(cert, key)
if cadata:
con.load_verify_locations(cadata=cadata)
con.verify_mode = cert_reqs
return con.wrap_socket(
sock,
server_side=server_side,
do_handshake_on_connect=do_handshake,
server_hostname=server_hostname,
)