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