Connecting the Pico W to the Web with Anvil

Anvil makes it almost magically simple to connect a Pico W to a web app. But how does it work under the hood? Let’s talk about Anvil, the Uplink, how it works and how we’ve made it secure.

Anvil is a platform for building web apps entirely in Python – Python in the browser, Python on the server, and even a Python UI toolkit with a drag-and-drop builder. The Pico Uplink allows you to connect your MicroPython code on the Pico to the Python code of your Anvil app, and make function calls between them. It makes it easy to control IoT projects with a cloud-based web app that’s hosted for you.

Communicating from the Pico to the cloud

When you turn on your Pico W using the Anvil firmware, it connects to WiFi and makes a secure websocket connection to Anvil, where your web app is hosted. From your Anvil app, you enable the Uplink and add an Uplink key. This is a secret key used to connect external code to your application. The Pico W authenticates using this key, which is passed to anvil.pico.connect().

You can register “callable” functions on the Pico with @anvil.pico.callable. When your Anvil app calls one of those functions, Anvil sends the function name and all its arguments down the websocket to the Pico. The Pico runs the function, then sends the result back to your app.

Likewise, when your Pico wants to call a function in your Anvil app, it sends the function name and arguments over the websocket, and waits for a response.

This means you can drive your microcontroller from your app (for example, to display Morse code on the LED). And you can also drive your app from your microcontroller (for example, to log data in your app’s database).

You can do it all without allowing incoming connections from the Internet to your device: The Pico has to be the one to open the websocket connection to the internet service. Once open, the Anvil server and Pico can communicate in both directions.

Of course, the Uplink isn’t just for microcontrollers. You can pip install the anvil-uplink library on full-size computers to connect Jupyter notebooks, access local databases and much more. Check out the Uplink documentation to learn more.


How we made it secure

It’s hard to make a typical homebrew IoT project secure, and easy to make mistakes. We needed the Uplink to be secure out-of-the-box, so you don’t have to think about it – so we made sure it always uses proper web encryption.

If you’re talking to a web service over the big scary Internet, you need to encrypt your traffic (so no third-party can see it), and authenticate your correspondent (so you know you’re not talking to some impostor). The standard way of doing this is with TLS, which is the protocol you use to talk to any secure website (that’s any URL starting https://).

The risks of impersonation

TLS uses cryptographic certificates to validate that you are talking to the server you intended to talk to. If I connect to mysecuredomain.com, that server had better present me with a valid certificate for that domain name, or I won’t go any further.

However, getting this right on a microcontroller like the Pico is hard. Out-of-the box, most embedded TLS development systems don’t validate certificates at all! This means that more or less anyone can intercept your connection, claim you you’re talking to the legitimate server for that domain, present a bogus certificate, and you won’t know. They can perform a “man-in-the-middle” attack and grab all your sensitive credentials.

So we implemented TLS certificate validation for the Pico W, meaning that our firmware does validate the server’s TLS certificate before sending anything sensitive. We’ve contributed that code back, of course, so hopefully other images get this soon.

Who’s my authority?

One snag: In order to trust that your counterparty is who they say they are, their TLS certificate needs to be signed by a Certificate Authority (CA for short), which vouches for their identity. A typical browser supports hundreds, which is a lot of data for the 2 megabytes of Flash in the Pico.

Fortunately, if we’re talking to Anvil’s own servers, we only need to trust the CA we use (Let’s Encrypt). So we bundled their certificate into our firmware image – job done.

The risks of time

So, are we benefiting from all the protections of TLS? Not quite.

Let’s assume that our encryption works and that it’s impossible to forge a CA signature. We’re still at risk if an attacker can produce a valid certificate for which they know the private key, so they can impersonate mytrustedserver.com to your code.

This can happen by tricking a CA into signing the attacker’s own malicious certificate for the victim domain, or by the attacker stealing the private key from the legitimate provider. Either way, if there’s a bad certificate in the wild, we’d prefer it not to be game-over. TLS provides two mechanisms to mitigate this risk: Expiry and revocation.

  • Expiry: each certificate has an expiry date, so you’ll need to periodically get a new certificate signed by the CA or nobody will talk TLS to you. (If you’re using an old-school CAs, this period is typically a year; for Let’s Encrypt it’s just 90 days.) So an attacker can’t get one bad certificate and use it forever: they have to keep getting new compromised certificates, which is much harder work and makes them much more likely to get caught.

  • Revocation: If you discover that someone has a bad certificate, you blacklist it so that nobody will trust it, even before it’s expired. In the desktop world, this uses complicated mechanisms like CRLs etc. Fortunately, for this library, there’s only one certificate we care about revoking. We can do that by releasing a new version of the library that blacklists the revoked cert.

Now, Expiry is remarkably tricky for embedded systems. The Pico, like many embedded systems, doesn’t remember the time while it’s switched off – its clock starts from zero each time it boots up. This makes it hard to detect whether a certificate is in date!

So we used NTP, the Network Time Protocol. Each time the Uplink boots up, it asks servers on the internet for the current time. It can then use that time to validate that it’s not being given an out-of-date TLS certificate.

This means that to mount a successful attack with an old, bad certificate, the attacker would have to compromise a certificate, and get on your WiFi network, at the moment your Pico is booting up to intercept the NTP requests and spoof responses.

Conclusion

To our knowledge, this is the most secure anyone has made a Pico W, and we’d be happy to send something built on this foundation into the world.

Of course, this entire effort builds on work from mbedtls and Micropython, and we’re happy to be contributing this work back so that anyone can create more secure MicroPython-based projects – even if they’re not using Anvil.