Getting Started: Raspberry Pi Pico W

Ian, you are now officially my hero of the month!!!

Indeed success now! For others arriving here: as mentioned above you need to get the certificate with openssl from the server, and when I did this, I had a file hivemq-com-chain.pem with three certificates inside it. Only the second one is the one you need for the conversation to hivemq-com-chain.der.

This is my full code that I just ran on my Pico W and validated that the message has arrived via the websocket client of HiveMQ! I will describe this further in a blog post and when ready add a link in this forum as a reference.

import time
import secrets
import network
from machine import Pin
from umqtt.simple import MQTTClient
import ussl
import ntptime

# Blink the onboard LED to show startup
led = Pin("LED", Pin.OUT)
led.off()
time.sleep_ms(50)
led.on()
time.sleep_ms(50)
led.off()
time.sleep_ms(50)
led.on()
time.sleep_ms(50)
led.off()

# Load the WiFi and HiveMQ Cloud credentials from secrets.py
try:
    from secrets import secrets
except ImportError:
    print("Error, secrets could not be read")
    raise

# Connect to WiFi
# Based on https://datasheets.raspberrypi.com/picow/connecting-to-the-internet-with-pico-w.pdf
print('----------------------------------------------------------------------------------------------')
print('Connecting to AP: ' + secrets["ssid"])
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(secrets["ssid"], secrets["password"])

# Wait for connect or fail
connected = False
attempt = 0
while not connected and attempt < 10:
    attempt += 1
    if wlan.status() < 0 or wlan.status() >= 3:
        connected = True
    if not connected:
        print("Connection attempt failed: " + str(attempt))
        time.sleep(1)
    else:
        print("Connected on attempt: " + str(attempt))
        
if not connected or wlan.ifconfig()[0] == "0.0.0.0":
    # Blink LED to show there is a WiFi problem
    print("Bad WiFi connection: " + wlan.ifconfig()[0])
    while True:
        led.off()
        time.sleep_ms(150)
        led.on()
        time.sleep_ms(150)

print("WiFi status: " + str(wlan.ifconfig()))
led.on()

# To validate certificates, a valid time is required
print('----------------------------------------------------------------------------------------------')
print('Connecting to NTP')
ntptime.host = "de.pool.ntp.org"
ntptime.settime()
print('Current time: ' + str(time.localtime()))

# Connect to HiveMQ
print('----------------------------------------------------------------------------------------------')
print('Loading CA Certificate')
with open("/certs/hivemq-com-chain_2_only.der", 'rb') as f:
    cacert = f.read()
f.close()
print('Obtained CA Certificate')

# Based on https://www.tomshardware.com/how-to/send-and-receive-data-raspberry-pi-pico-w-mqtt
print('----------------------------------------------------------------------------------------------')
print("Connecting to " + secrets["broker"] + " as user " + secrets["mqtt_username"])
sslparams = {'server_side': False,
             'key': None,
             'cert': None,
             'cert_reqs': ussl.CERT_REQUIRED,
             'cadata': cacert,
             'server_hostname': secrets["broker"]}
client = MQTTClient(client_id="picow",
                    server=secrets["broker"],
                    port=secrets["port"],
                    user=secrets["mqtt_username"],
                    password=secrets["mqtt_key"],
                    keepalive=3600,
                    ssl=True,
                    ssl_params=sslparams) 
client.connect()
print('Connected to MQTT Broker: ' + secrets["broker"])

# Send a message to HiveMQ
client.publish('test', 'lalalala')

Output:

----------------------------------------------------------------------------------------------
Connecting to AP: ***
Connected on attempt: 1
WiFi status: ('172.16.1.88', '255.255.255.0', '172.16.1.1', '172.16.1.1')
----------------------------------------------------------------------------------------------
Connecting to NTP
Current time: (2022, 9, 10, 15, 21, 57, 5, 253)
----------------------------------------------------------------------------------------------
Loading CA Certificate
Obtained CA Certificate
----------------------------------------------------------------------------------------------
Connecting to f250***.s1.eu.hivemq.cloud as user picow-user
Connected to MQTT Broker: f250***.s1.eu.hivemq.cloud

Seeing is believing :wink:

2 Likes

Hi Frank & Ian - Apologies for the late reply as Iā€™ve been away from my computer for a couple of days. Thanks for pointing us in the right direction Ian - I, too, now have it working.

However, with a little further experimenting, it looks like the cert is not required - itā€™s the ā€˜server_hostnameā€™ attribute which is the critical part that we were missing. It works for me by using just this:

ssl_params = {'server_hostname': '<SERVER>.s1.eu.hivemq.cloud'}

We donā€™t need the cert at all!.

1 Like

@Prolixteriat Whilst it is true you do not require the certificate verification to get the TLS connection working unfortunately that would NOT be very secure. We need to ensure the remote server presents a valid certificate which has been issued by a Trust CA not just self-signed !!!

1 Like

Tested and confirmed! only added server_hostname is sufficient. But also agree on security awarenessā€¦

Good point, well made :slightly_smiling_face:

1 Like

There are three -----BEGIN CERTIFICATE------s. Which one should be kept? First, second, or third?

serveraddress = ...
!openssl s_client -connect {server_address}.hivemq.cloud:8883 -showcerts < /dev/null 2> /dev/null > hivemq-output.txt
CONNECTED(00000005)
---
Certificate chain
 0 s:CN = *.s2.eu.hivemq.cloud
   i:C = US, O = Let's Encrypt, CN = R3
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
 1 s:C = US, O = Let's Encrypt, CN = R3
   i:C = US, O = Internet Security Research Group, CN = ISRG Root X1
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
 2 s:C = US, O = Internet Security Research Group, CN = ISRG Root X1
   i:O = Digital Signature Trust Co., CN = DST Root CA X3
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
---
Server certificate
subject=CN = *.s2.eu.hivemq.cloud

issuer=C = US, O = Let's Encrypt, CN = R3

---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: RSA-PSS
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 4520 bytes and written 446 bytes
Verification: OK
---
New, TLSv1.2, Cipher is ...
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ...
    Session-ID: ...
    Session-ID-ctx: 
    Master-Key: ...
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    Start Time: ...
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
    Extended master secret: yes
---

EDIT: seems to be the second one

I made a Google Colab script that handles the openssl certificate generation process:

Copying the code here for provenance:

# dummy server address: "1234abc5678def91011ghi1213.s2.eu"
serveraddress = "" #@param {type:"string"}
port = 8883 #@param {type:"integer"}

!openssl s_client -connect {serveraddress}.hivemq.cloud:{port} -showcerts < /dev/null 2> /dev/null > hivemq-output.txt

with open("hivemq-output.txt", "r") as f:
  txt = f.read()
print(txt)

begin_str = "-----BEGIN CERTIFICATE-----"
end_str = "-----END CERTIFICATE-----"
occurrence = 1 # second
server_cert = txt.split(begin_str)[occurrence + 1].split(end_str)[0]

pem_fpath = "hivemq-com-chain.pem"
with open(pem_fpath, "w") as f:
  f.write(begin_str)
  f.write(server_cert)
  f.write(end_str)

with open(pem_fpath, "r") as f:
  print(f.read())

der_fpath = "hivemq-com-chain.der"
!openssl x509 -in {pem_fpath} -out {der_fpath} -outform DER

Then just download the file and upload to the Pico W

1 Like

Iā€™ve been using a certificate to communicate between a Pico W microcontroller and a HiveMQ cluster for a while now. In a course Iā€™m developing, Iā€™m instructing learners to create their own HiveMQ cluster and generate their own certificate via the Colab script above. Would you consider this step necessary? I am a novice when it comes to certificate authorities (CAs). My main intention is to have a private (encrypted), secure, communication channel over WiFi between two devices, one of which is a Pico W.

Since I use paho-mqtt on an ā€œorchestratorā€ client to send commands to and receive data from a Pico W microcontroller client, I would also need to specify the same certificate (though in .pem format instead of .der format, also available during the Colab script generation) as follows, correct?

import paho.mqtt.client as mqtt

def on_connect(client, userdata, flags, rc):
    print("Connected with result code " + str(rc))

client = mqtt.Client()
client.on_connect = on_connect

# Configure SSL/TLS
client.tls_set(ca_certs="/path/to/hivemq-com-chain.pem")

# # Uncomment if using cluster-specific credentials
# client.username_pw_set(username, password)

# Connect to the broker
client.connect("abc123***.s1.eu.hivemq.cloud", 8883, 60)

# Start the loop
client.loop_forever()

EDIT: I used my Colab notebook and tried with a real host and several dummy hosts (e.g., abc.s2.eu.hivemq.cloud). The certificates are identical. I guess I was playing the fool a bit :sweat_smile:. Granted, someone should still generate the certificate themselves (i.e., trust using openssl as installed and used on Google Colab directly rather than trust a file that someone else claims was generated properly using openssl).

Some related posts:

Hi @sgbaird

You can download the CA file from https://letsencrypt.org/certs/isrgrootx1.pem

I hope it helps.

Dasha from HiveMQ Team

Looks like Iā€™m unable to edit my prior posts, so Iā€™ll put all of this in a reply. It seems that recently something on HiveMQ Cloud changed such that the .der certificate generated for one broker instance is not transferable to another. I verified this with two brokers, where the .der files were generated via a Google Colab notebook mentioned previously.

Broker 1 Broker 2
Broker 1 .der file :white_check_mark: :x:
Broker 2 .der file :x: :white_check_mark:

From a HiveMQ blogpost on using Paho MQTT, it clarifies the following related to TLS/SSL.

The client supports certificate based TLS. You should call the tls_set() function to enable TLS mode and to specify the CA certificates that you trust.

client.tls_set('/path/to/ca.crt')

It is also possible to set a client certificate, the certificate verification requirements, the version of TLS to use and the allowable ciphers: tls_set(self, ca_certs, certfile=None, keyfile=None, cert_reqs=cert_reqs, tls_version=tls_version, ciphers=None)

Full details of these parameters is given in the client documentation.

You shared a PEM file (from what I understand, this canā€™t be used on the Pico W, and Iā€™m not sure how this would be used within Paho MQTT). Can you clarify how I would generate the the ca.crt file described in the above example, or would I simply swap out the ca.crt file with the .pem file you shared? Likewise, would I need to do any customization with the parameters ca_certs, certfile, etc.? Iā€™m not too familiar with security and encryption, but am trying to maintain private, secure, and encrypted communication over the HiveMQ broker I use, between a Pico W and a Python runtime.

python - Passing crt/pem with paho - Stack Overflow would seem to suggest that passing the path to the PEM file is fine.

Related:

here a video with rp2040, the same microcontroller

1 Like

Update: the certificate generated via openssl also seems to be expiring periodically (every couple months or so?). Frequent enough for me to dig back in to try to figure this out.

EDIT: ChatGPT suggested running the following to check the certificate expiration, and suggested that the certificate expiration is set on the server side.

openssl s_client -connect {host}:{port} 2>/dev/null | openssl x509 -noout -enddate

I ran this in the same Colab notebook, and got: notAfter=Jan 6 10:22:14 2025 GMT

So it looks like every couple months? Not sure if this is at specific times throughout the year, or if itā€™s some period of time after the certificate has been generated. Normally I would assume the latter, but running the notebook from the beginning a few minutes later gave the exact same output.

Hi @sgbaird

You can download the CA root certificate here: https://letsencrypt.org/certs/isrgrootx1.pem

I hope it helps,
Best,
Dasha from The HiveMQ Team

Ok, maybe all I need to do then is convert from PEM to DER format (my understanding is that PEM is not a supported format), which appears to be via the following command:

openssl x509 -outform der -in <certificate file name>.pem -out <certificate file name>.der

I will try and report back. Also, does it present a security issue to use the HiveMQ root certificate compared with generating one specific to the userā€™s broker instance?