I ran into trouble with SSL certificate errors when sending web requests from CircuitPython. This is a description of the problem and how I solved it.
Problem
When sending HTTPS requests to a web server using CircuitPython’s adafruit_requests
library, it may fail to verify the server’s SSL certificate even though the certificate is valid. The error messages from CircuitPython look like this:
Failed to verify certificate
OSError: (-12288, 'MBEDTLS_ERR_X509_FATAL_ERROR')
The problem is that CircuitPython’s ssl
library does not know about all of the root CAs (certificate authorities) that issue SSL certificates, so some web sites have SSL certificates that can’t be verified. CircuitPython’s hard-coded list of root certificates is incomplete due to memory limitations.
My Example
I ran into this problem with my asentry project, which uses a Raspbery Pi Pico W to connect to a NASA web service. It worked fine for months, then suddenly failed with the error message shown above. The web server could still be accessed using desktop Python or a web browser, so the problem was apparently in CircuitPython.
The cause of the problem turned out to be that the web server’s SSL certificate was updated, and the new certificate referred to a different root CA which happened to be one that is not supported by CircuitPython.
Solution
Fortunately, there is a way to fix this, for most applications anyway.
CircuitPython’s ssl
library provides the function load_verify_locations()
that can load a certificate for a root CA and use it to verify web sites’ SSL certificates. There is a limitation (as of CircuitPython 9.2.6): Only a single root certificate can be loaded, so this only works for an application that accesses a single web site, not multiple diffrerent web sites (whose SSL certificates will likely refer to multiple different CAs).
Here’s how to make sure the correct root CA certificate is used:
Make sure the web site is working
First, make sure that the problematic web site’s SSL certificate is actually valid. Enter the web site’s HTTPS URL into a web browser and check that the site comes up without an error.
If this fails, the problem is on the server side. Sorry, I can’t help with that!
Find and download the root certificate
Once you have the web site displayed in a browser, you can get a copy of the root CA’s certificate that was used to verify the HTTPS connection.
To do this, click on the padlock icon in the browser’s address bar, then click around until you figure out how to view the site’s SSL certificate chain. Select the root certificate (top-most or right-most in the list) and save it to a file.
(The details of how to do this depend on which web browser you use.)
You should end up with a file containing a single certificate (not the entire certificate chain) that starts with -----BEGIN CERTIFICATE-----
followed by a bunch of base64-encoded stuff.
Use the correct root certificate
The final step is to get CircuitPython to use the correct root certificate.
Here is a snippet of code that initializes the adafruit_requests
library using a specific SSL root certificate:
ssl_context = ssl.create_default_context()
ssl_cert = '-----BEGIN CERTIFICATE-----\n'\
'MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBO\n'\
'MQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQD\n'\
…several lines omitted…
'98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA=\n'\
'-----END CERTIFICATE-----\n'
ssl_context.load_verify_locations(cadata=ssl_cert)
requests = adafruit_requests.Session(socketpool.SocketPool(radio),
ssl_context)
The variable ssl_cert
contains the entire contents of the certificate file that was downloaded. Note that the line breaks from the file are preserved (the \n
at the end of each line).
See here for a complete program that loads a root certificate in this way.