Skip to content

Commit 30f8598

Browse files
authored
Merge pull request #79 from arduino/ssl_context
ucloud: Switch to SSLConext.
2 parents a8dd722 + b15916f commit 30f8598

File tree

5 files changed

+95
-81
lines changed

5 files changed

+95
-81
lines changed

examples/example.py

+17-12
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 ssl
14+
import ssl # noqa
1515

1616
from secrets import DEVICE_ID
1717
from secrets import SECRET_KEY # noqa
@@ -58,17 +58,22 @@ def user_task(client):
5858
)
5959

6060
# Create a client object to connect to the Arduino IoT cloud.
61-
# To use a secure element, set the token's "pin" and URI in "keyfile" and "certfile", and
62-
# the CA certificate (if any) in "ssl_params". Alternatively, a username and password can
63-
# be used to authenticate, for example:
64-
# client = ArduinoCloudClient(device_id=DEVICE_ID, username=DEVICE_ID, password=SECRET_KEY)
65-
client = ArduinoCloudClient(
66-
device_id=DEVICE_ID,
67-
ssl_params={
68-
"pin": "1234",
69-
"keyfile": KEY_PATH, "certfile": CERT_PATH, "ca_certs": CA_PATH, "cert_reqs": ssl.CERT_REQUIRED
70-
},
71-
)
61+
# The most basic authentication method uses a username and password. The username is the device
62+
# ID, and the password is the secret key obtained from the IoT cloud when provisioning a device.
63+
client = ArduinoCloudClient(device_id=DEVICE_ID, username=DEVICE_ID, password=SECRET_KEY)
64+
65+
# Alternatively, the client also supports key and certificate-based authentication. To use this
66+
# mode, set "keyfile" and "certfile", and the CA certificate (if any) in "ssl_params".
67+
# Furthermore, secure elements, which can be used to store the key and cert, are also supported.
68+
# To secure elements, set "use_hsm" to True in "ssl_params" and set the token's "pin" if any.
69+
# client = ArduinoCloudClient(
70+
# device_id=DEVICE_ID,
71+
# ssl_params={
72+
# "use_hsm": True, "pin": "1234",
73+
# "keyfile": KEY_PATH, "certfile": CERT_PATH, "cafile": CA_PATH,
74+
# "verify_mode": ssl.CERT_REQUIRED, "server_hostname" : "iot.arduino.cc"
75+
# },
76+
# )
7277

7378
# Register cloud objects.
7479
# Note: The following objects must be created first in the dashboard and linked to the device.

examples/micropython_async_wifi.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ def on_switch_changed(client, value):
4242
)
4343

4444
# Create a client object to connect to the Arduino IoT cloud.
45-
# For MicroPython, the key and cert files must be stored in DER format on the filesystem.
46-
# Alternatively, a username and password can be used to authenticate:
45+
# The most basic authentication method uses a username and password. The username is the device
46+
# ID, and the password is the secret key obtained from the IoT cloud when provisioning a device.
4747
client = ArduinoCloudClient(
4848
device_id=DEVICE_ID, username=DEVICE_ID, password=SECRET_KEY
4949
)

examples/micropython_basic.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,19 @@ def wifi_connect():
7373
wifi_connect()
7474

7575
# Create a client object to connect to the Arduino IoT cloud.
76-
# For MicroPython, the key and cert files must be stored in DER format on the filesystem.
77-
# Alternatively, a username and password can be used to authenticate:
76+
# The most basic authentication method uses a username and password. The username is the device
77+
# ID, and the password is the secret key obtained from the IoT cloud when provisioning a device.
7878
client = ArduinoCloudClient(device_id=DEVICE_ID, username=DEVICE_ID, password=SECRET_KEY)
79+
80+
# Alternatively, the client also supports key and certificate-based authentication. To use this
81+
# mode, set "keyfile" and "certfile", and the CA certificate (if any) in "ssl_params".
82+
# Note that for MicroPython, the key and cert files must be stored in DER format on the filesystem.
7983
# client = ArduinoCloudClient(
8084
# device_id=DEVICE_ID,
8185
# ssl_params={
82-
# "keyfile": KEY_PATH, "certfile": CERT_PATH, "cadata": CADATA, "cert_reqs": ssl.CERT_REQUIRED
83-
# }
86+
# "keyfile": KEY_PATH, "certfile": CERT_PATH, "cadata": CADATA,
87+
# "verify_mode": ssl.CERT_REQUIRED, "server_hostname" : "iot.arduino.cc"
88+
# },
8489
# )
8590

8691
# Register cloud objects.

src/arduino_iot_cloud/umqtt.py

+7-9
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import select
2828
import logging
2929
import arduino_iot_cloud.ussl as ssl
30+
import sys
3031

3132

3233
class MQTTException(Exception):
@@ -92,17 +93,14 @@ def connect(self, clean_session=True, timeout=5.0):
9293
self.sock.close()
9394
self.sock = None
9495

95-
try:
96-
self.sock = socket.socket()
97-
self.sock.settimeout(timeout)
98-
self.sock = ssl.wrap_socket(self.sock, self.ssl_params)
99-
self.sock.connect(addr)
100-
except Exception:
101-
self.sock.close()
102-
self.sock = socket.socket()
103-
self.sock.settimeout(timeout)
96+
self.sock = socket.socket()
97+
self.sock.settimeout(timeout)
98+
if sys.implementation.name == "micropython":
10499
self.sock.connect(addr)
105100
self.sock = ssl.wrap_socket(self.sock, self.ssl_params)
101+
else:
102+
self.sock = ssl.wrap_socket(self.sock, self.ssl_params)
103+
self.sock.connect(addr)
106104

107105
premsg = bytearray(b"\x10\0\0\0\0\0")
108106
msg = bytearray(b"\x04MQTT\x04\x02\0\0")

src/arduino_iot_cloud/ussl.py

+60-54
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
#
77
# SSL module with m2crypto backend for HSM support.
88

9-
import sys
109
import ssl
1110

1211
pkcs11 = None
@@ -16,62 +15,69 @@
1615
_MODULE_PATH = "/usr/lib/softhsm/libsofthsm2.so"
1716

1817

19-
def wrap_socket(
20-
sock,
21-
ssl_params={},
22-
):
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
36-
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()
47-
48-
# Create and configure SSL context
49-
ctx = SSL.Context("tls")
50-
ctx.set_default_verify_paths()
51-
ctx.set_allow_unknown_ca(False)
52-
18+
def wrap_socket(sock, ssl_params={}):
19+
keyfile = ssl_params.get("keyfile", None)
20+
certfile = ssl_params.get("certfile", None)
21+
cafile = ssl_params.get("cafile", None)
22+
cadata = ssl_params.get("cadata", None)
5323
ciphers = ssl_params.get("ciphers", None)
54-
if ciphers is not None:
55-
ctx.set_cipher_list(ciphers)
24+
verify = ssl_params.get("verify_mode", ssl.CERT_NONE)
25+
hostname = ssl_params.get("server_hostname", None)
26+
use_hsm = ssl_params.get("use_hsm", False)
5627

57-
ca_certs = ssl_params.get("ca_certs", None)
58-
if ca_certs is not None:
59-
if ctx.load_verify_locations(ca_certs) != 1:
60-
raise Exception("Failed to load CA certs")
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
28+
if not use_hsm:
29+
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
30+
if hasattr(ctx, "set_default_verify_paths"):
31+
ctx.set_default_verify_paths()
32+
if verify != ssl.CERT_REQUIRED:
33+
ctx.check_hostname = False
34+
ctx.verify_mode = verify
35+
if keyfile is not None and certfile is not None:
36+
ctx.load_cert_chain(certfile, keyfile)
37+
if ciphers is not None:
38+
ctx.set_ciphers(ciphers)
39+
if cafile is not None or cadata is not None:
40+
ctx.load_verify_locations(cafile, cadata)
41+
return ctx.wrap_socket(sock, server_hostname=hostname)
6542
else:
66-
cert_reqs = SSL.verify_peer
67-
ctx.set_verify(cert_reqs, depth=9)
43+
# Use M2Crypto to load key and cert from HSM.
44+
from M2Crypto import m2, SSL, Engine
45+
46+
global pkcs11
47+
if pkcs11 is None:
48+
pkcs11 = Engine.load_dynamic_engine(
49+
"pkcs11", ssl_params.get("engine_path", _ENGINE_PATH)
50+
)
51+
pkcs11.ctrl_cmd_string(
52+
"MODULE_PATH", ssl_params.get("module_path", _MODULE_PATH)
53+
)
54+
if "pin" in ssl_params:
55+
pkcs11.ctrl_cmd_string("PIN", ssl_params["pin"])
56+
pkcs11.init()
57+
58+
# Create and configure SSL context
59+
ctx = SSL.Context("tls")
60+
ctx.set_default_verify_paths()
61+
ctx.set_allow_unknown_ca(False)
62+
if verify == ssl.CERT_NONE:
63+
ctx.set_verify(SSL.verify_none, depth=9)
64+
else:
65+
ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, depth=9)
66+
if cafile is not None:
67+
if ctx.load_verify_locations(cafile) != 1:
68+
raise Exception("Failed to load CA certs")
69+
if ciphers is not None:
70+
ctx.set_cipher_list(ciphers)
6871

69-
# Set key/cert
70-
key = pkcs11.load_private_key(ssl_params["keyfile"])
71-
m2.ssl_ctx_use_pkey_privkey(ctx.ctx, key.pkey)
72+
key = pkcs11.load_private_key(keyfile)
73+
m2.ssl_ctx_use_pkey_privkey(ctx.ctx, key.pkey)
7274

73-
cert = pkcs11.load_certificate(ssl_params["certfile"])
74-
m2.ssl_ctx_use_x509(ctx.ctx, cert.x509)
75+
cert = pkcs11.load_certificate(certfile)
76+
m2.ssl_ctx_use_x509(ctx.ctx, cert.x509)
7577

76-
SSL.Connection.postConnectionCheck = None
77-
return SSL.Connection(ctx, sock=sock)
78+
sslobj = SSL.Connection(ctx, sock=sock)
79+
if verify == ssl.CERT_NONE:
80+
sslobj.clientPostConnectionCheck = None
81+
elif hostname is not None:
82+
sslobj.set1_host(hostname)
83+
return sslobj

0 commit comments

Comments
 (0)