Skip to content

Commit 15452c5

Browse files
committed
ussl: Clean up SSL and m2crypto code.
- Move all SSL code to ussl module. - Allow the client to be used without installing m2crypto if it's not needed.
1 parent 1480faf commit 15452c5

File tree

6 files changed

+61
-62
lines changed

6 files changed

+61
-62
lines changed

examples/example.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from arduino_iot_cloud import Task
1212
from random import uniform
1313
import argparse
14-
import arduino_iot_cloud.ussl as ssl
14+
import ssl
1515

1616
from secrets import DEVICE_ID
1717
from secrets import SECRET_KEY # noqa

examples/micropython.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Any copyright is dedicated to the Public Domain.
33
# https://creativecommons.org/publicdomain/zero/1.0/
44
import time
5-
import ussl
5+
import ssl
66
import network
77
import logging
88
from time import strftime
@@ -73,7 +73,7 @@ def wifi_connect():
7373
client = ArduinoCloudClient(
7474
device_id=DEVICE_ID,
7575
ssl_params={
76-
"keyfile": KEY_PATH, "certfile": CERT_PATH, "cadata": CADATA, "cert_reqs": ussl.CERT_REQUIRED
76+
"keyfile": KEY_PATH, "certfile": CERT_PATH, "cadata": CADATA, "cert_reqs": ssl.CERT_REQUIRED
7777
}
7878
)
7979

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ classifiers = [
2222
]
2323
dependencies = [
2424
'cbor2 >= 5.4.6',
25-
'M2Crypto >= 0.38.0',
2625
'micropython-senml >= 0.1.0',
2726
]
2827

src/arduino_iot_cloud/ucloud.py

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
66

77
import time
8+
import sys
89
import logging
910
from senml import SenmlPack
1011
from senml import SenmlRecord
@@ -175,6 +176,14 @@ def __init__(
175176
self.senmlpack = SenmlPack("", self.senml_generic_callback)
176177
self.started = False
177178

179+
if "pin" in ssl_params:
180+
try:
181+
# Use M2Crypto to load key and cert from HSM.
182+
import M2Crypto
183+
except (ImportError, AttributeError):
184+
logging.error("The m2crypto module is required to use HSM.")
185+
sys.exit(1)
186+
178187
# Convert args to bytes if they are passed as strings.
179188
if isinstance(device_id, str):
180189
device_id = bytes(device_id, "utf-8")
@@ -188,19 +197,6 @@ def __init__(
188197
# Update RTC from NTP server on MicroPython.
189198
self.update_systime(ntp_server, ntp_timeout)
190199

191-
# MicroPython does not support secure elements yet, and key/cert
192-
# must be loaded from DER files and passed as binary blobs.
193-
if "keyfile" in ssl_params and "der" in ssl_params["keyfile"]:
194-
with open(ssl_params.pop("keyfile"), "rb") as f:
195-
ssl_params["key"] = f.read()
196-
if "certfile" in ssl_params and "der" in ssl_params["certfile"]:
197-
with open(ssl_params.pop("certfile"), "rb") as f:
198-
ssl_params["cert"] = f.read()
199-
200-
if "ca_certs" in ssl_params and "der" in ssl_params["ca_certs"]:
201-
with open(ssl_params.pop("ca_certs"), "rb") as f:
202-
ssl_params["cadata"] = f.read()
203-
204200
# If no server/port were passed in args, set the default server/port
205201
# based on authentication type.
206202
if server is None:

src/arduino_iot_cloud/umqtt.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,7 @@
2626
import struct
2727
import select
2828
import logging
29-
30-
try:
31-
from ussl import wrap_socket
32-
except ImportError:
33-
from arduino_iot_cloud.ussl import wrap_socket
29+
import arduino_iot_cloud.ussl as ssl
3430

3531

3632
class MQTTException(Exception):
@@ -99,14 +95,14 @@ def connect(self, clean_session=True, timeout=5.0):
9995
try:
10096
self.sock = socket.socket()
10197
self.sock.settimeout(timeout)
102-
self.sock = wrap_socket(self.sock, **self.ssl_params)
98+
self.sock = ssl.wrap_socket(self.sock, self.ssl_params)
10399
self.sock.connect(addr)
104100
except Exception:
105101
self.sock.close()
106102
self.sock = socket.socket()
107103
self.sock.settimeout(timeout)
108104
self.sock.connect(addr)
109-
self.sock = wrap_socket(self.sock, **self.ssl_params)
105+
self.sock = ssl.wrap_socket(self.sock, self.ssl_params)
110106

111107
premsg = bytearray(b"\x10\0\0\0\0\0")
112108
msg = bytearray(b"\x04MQTT\x04\x02\0\0")

src/arduino_iot_cloud/ussl.py

Lines changed: 46 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,64 +6,72 @@
66
#
77
# SSL module with m2crypto backend for HSM support.
88

9-
from M2Crypto import Engine, m2, SSL
9+
import sys
10+
import ssl
1011

11-
CERT_NONE = SSL.verify_none
12-
CERT_REQUIRED = SSL.verify_peer
13-
14-
_key = None
15-
_cert = None
12+
pkcs11 = None
1613

1714
# Default engine and provider.
1815
_ENGINE_PATH = "/usr/lib/engines-3/libpkcs11.so"
1916
_MODULE_PATH = "/usr/lib/softhsm/libsofthsm2.so"
2017

2118

22-
def init(pin, certfile, keyfile, engine_path, module_path):
23-
global _key, _cert
24-
Engine.load_dynamic_engine("pkcs11", engine_path)
25-
pkcs11 = Engine.Engine("pkcs11")
26-
pkcs11.ctrl_cmd_string("MODULE_PATH", module_path)
27-
pkcs11.ctrl_cmd_string("PIN", pin)
28-
pkcs11.init()
29-
_key = pkcs11.load_private_key(keyfile)
30-
_cert = pkcs11.load_certificate(certfile)
31-
32-
3319
def wrap_socket(
34-
sock_in,
35-
pin=None,
36-
certfile=None,
37-
keyfile=None,
38-
ca_certs=None,
39-
cert_reqs=CERT_NONE,
40-
ciphers=None,
41-
engine_path=_ENGINE_PATH,
42-
module_path=_MODULE_PATH,
20+
sock,
21+
ssl_params={},
4322
):
44-
if certfile is None or keyfile is None:
45-
# Fallback to Python's SSL
46-
import ssl
47-
return ssl.wrap_socket(sock_in)
23+
if any(k not in ssl_params for k in ("keyfile", "certfile", "pin")):
24+
# Use Micro/CPython's SSL
25+
if sys.implementation.name == "micropython":
26+
# Load key, cert and CA from DER files, and pass them as binary blobs.
27+
mpargs = {"keyfile": "key", "certfile": "cert", "ca_certs": "cadata"}
28+
for k, v in mpargs.items():
29+
if k in ssl_params and "der" in ssl_params[k]:
30+
with open(ssl_params.pop(k), "rb") as f:
31+
ssl_params[v] = f.read()
32+
return ssl.wrap_socket(sock, **ssl_params)
33+
34+
# Use M2Crypto to load key and cert from HSM.
35+
from M2Crypto import m2, SSL, Engine
4836

49-
if _key is None or _cert is None:
50-
init(pin, certfile, keyfile, engine_path, module_path)
37+
global pkcs11
38+
if pkcs11 is None:
39+
pkcs11 = Engine.load_dynamic_engine(
40+
"pkcs11", ssl_params.get("engine_path", _ENGINE_PATH)
41+
)
42+
pkcs11.ctrl_cmd_string(
43+
"MODULE_PATH", ssl_params.get("module_path", _MODULE_PATH)
44+
)
45+
pkcs11.ctrl_cmd_string("PIN", ssl_params["pin"])
46+
pkcs11.init()
5147

52-
# Create SSL context
48+
# Create and configure SSL context
5349
ctx = SSL.Context("tls")
5450
ctx.set_default_verify_paths()
5551
ctx.set_allow_unknown_ca(False)
5652

53+
ciphers = ssl_params.get("ciphers", None)
5754
if ciphers is not None:
5855
ctx.set_cipher_list(ciphers)
5956

60-
if ca_certs is not None and cert_reqs is not CERT_NONE:
57+
ca_certs = ssl_params.get("ca_certs", None)
58+
if ca_certs is not None:
6159
if ctx.load_verify_locations(ca_certs) != 1:
6260
raise Exception("Failed to load CA certs")
63-
ctx.set_verify(SSL.verify_peer, depth=9)
61+
62+
cert_reqs = ssl_params.get("cert_reqs", ssl.CERT_NONE)
63+
if cert_reqs == ssl.CERT_NONE:
64+
cert_reqs = SSL.verify_none
65+
else:
66+
cert_reqs = SSL.verify_peer
67+
ctx.set_verify(cert_reqs, depth=9)
6468

6569
# Set key/cert
66-
m2.ssl_ctx_use_x509(ctx.ctx, _cert.x509)
67-
m2.ssl_ctx_use_pkey_privkey(ctx.ctx, _key.pkey)
70+
key = pkcs11.load_private_key(ssl_params["keyfile"])
71+
m2.ssl_ctx_use_pkey_privkey(ctx.ctx, key.pkey)
72+
73+
cert = pkcs11.load_certificate(ssl_params["certfile"])
74+
m2.ssl_ctx_use_x509(ctx.ctx, cert.x509)
75+
6876
SSL.Connection.postConnectionCheck = None
69-
return SSL.Connection(ctx, sock=sock_in)
77+
return SSL.Connection(ctx, sock=sock)

0 commit comments

Comments
 (0)