-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Add support for SSL client certificate (load_cert_chain) and self-signed certificate (load_verify_locations) #7029
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Tested with badssl.com: 1. Get client certificates from https://badssl.com/download/ 2. Convert public portion with `openssl x509 -in badssl.com-client.pem -out CIRCUITPY/cert.pem` 3. Convert private portion with `openssl rsa -in badssl.com-client.pem -out CIRCUITPY/privkey.pem` and the password `badssl.com` 4. Put wifi settings in CIRCUITPY/.env 5. Run the below Python script: ```py import os import wifi import socketpool import ssl import adafruit_requests TEXT_URL = "https://client.badssl.com/" wifi.radio.connect(os.getenv('WIFI_SSID'), os.getenv('WIFI_PASSWORD')) pool = socketpool.SocketPool(wifi.radio) context = ssl.create_default_context() requests = adafruit_requests.Session(pool, context) print(f"Fetching from {TEXT_URL} without certificate (should fail)") response = requests.get(TEXT_URL) print(f"{response.status_code=}, should be 400 Bad Request") input("hit enter to continue\r") print("Loading client certificate") context.load_cert_chain("/cert.pem", "privkey.pem") requests = adafruit_requests.Session(pool, context) print(f"Fetching from {TEXT_URL} with certificate (should succeed)") response = requests.get(TEXT_URL) print(f"{response.status_code=}, should be 200 OK") ```
## Testing self-signed certificates and `load_verify_locations` Obtain the badssl "self-signed" certificate in the correct form: ```sh openssl s_client -servername self-signed.badssl.com -connect untrusted-root.badssl.com:443 < /dev/null | openssl x509 > self-signed.pem ``` Copy it and the script to CIRCUITPY: ```python import os import wifi import socketpool import ssl import adafruit_requests TEXT_URL = "https://self-signed.badssl.com/" if not wifi.radio.ipv4_address: wifi.radio.connect(os.getenv('WIFI_SSID'), os.getenv('WIFI_PASSWORD')) pool = socketpool.SocketPool(wifi.radio) context = ssl.create_default_context() requests = adafruit_requests.Session(pool, context) print(f"Fetching from {TEXT_URL} without certificate (should fail)") try: response = requests.get(TEXT_URL) except Exception as e: print(f"Failed: {e}") else: print(f"{response.status_code=}, should have failed with exception") print("Loading server certificate") with open("/self-signed.pem", "rb") as certfile: context.load_verify_locations(cadata=certfile.read()) requests = adafruit_requests.Session(pool, context) print(f"Fetching from {TEXT_URL} with certificate (should succeed)") try: response = requests.get(TEXT_URL) except Exception as e: print(f"Unexpected exception: {e}") else: print(f"{response.status_code=}, should be 200 OK") ```
Self-signed cert didn't work at first, then realized the cert was not only self-signed but expired. Re-generated a new cert and boom:
w00t! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same server is also accessible via mDNS:
Loading server certificate
Fetching from https://redacted.local with certificate (should succeed)
response.status_code=200, should be 200 OK
For future reference... to make this work, I added to the code:
context.check_hostname = False
and also removed the trailing period from the mDNS name (we don't seem to handle that):
redacted.local.
--> redacted.local
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code structure looks good. Leaving to anecdata to test functionality, which looks good from here also. I have not used certs in this way for 8 or 9 years.
I don't really know much about certs, just found the right GUI bits on the device to plow through it. Hopefully someone doing AWS IoT or something similar can test the first part (after merge even). |
I tried the Testing self-signed certificates and load_verify_locations instructions on an ESP32 S2 mini clone (identified as an ESP32 S2 feather chip) running adafruit-circuitpython-adafruit_feather_esp32s2-sv-8.2.9.uf2 and it works. To use it with your own hostname, replace the host name with your own when you retrieve the certificate file: Just a plan IP address did not work for me, but a local network host name did. However, to make HTTPS requests using self-signed certificates I need to download and put the certificate .pem file on the device? There is no way I can just bypass SSL checks like cURL does with the I thought context.check_hostname = False bypassed the checks, but apparently not, I need to supply the .pem file, otherwise I get |
Tested with badssl.com on raspberry pi pico w & espressif esp32s3-eye
Testing SSL client certificates
The code and the required .pem files are in key-test.zip but you can convert them using the steps below (I'm not sure how frequently these keys are regenerated, better to do it yourself):
pem
format from https://badssl.com/download/: https://badssl.com/certs/badssl.com-client.pemopenssl x509 -in badssl.com-client.pem -out CIRCUITPY/cert.pem
openssl rsa -in badssl.com-client.pem -out CIRCUITPY/privkey.pem
and the passwordbadssl.com
Closes: #7002
Testing self-signed certificates and
load_verify_locations
Obtain the badssl "self-signed" certificate in the correct form:
Copy it and the script to CIRCUITPY:
Closes: #7025