sparks-baird / self-driving-lab-demo

Software and instructions for setting up and running a self-driving lab (autonomous experimentation) demo using dimmable RGB LEDs, an 8-channel spectrophotometer, a microcontroller, and an adaptive design algorithm, as well as extensions to liquid- and solid-based color matching demos.
https://self-driving-lab-demo.readthedocs.io/
MIT License
71 stars 8 forks source link

public-private key for hardware validation #133

Closed sgbaird closed 1 year ago

sgbaird commented 1 year ago

Related:

Based on discussion with @rekumar https://github.com/sparks-baird/self-driving-lab-demo/discussions/127#discussioncomment-4198692:

  1. Maybe a public-private key encryption scheme (like RSA, python example) is appropriate here? Example: the private key lives on your hardware device, the public key is the device identifier within your database. Any data generated by the device is terminated with some signature, then encrypted using the private key. Your database API then decrypts the data using the public key and checks for the signature before accepting the new data.

As a bonus, you can use these keys to do device-side validation (using its private key) to block instructions from bad actors. Instructions sent to the device would be encrypted using its public key, then decrypted on the device side to check the signature. In this scenario, maybe you don't want to use the public key as the device's ID in the database... you could encrypt the device's ID using a server-side private key to hide this from external viewers of the database.

https://github.com/sparks-baird/self-driving-lab-demo/discussions/127#discussioncomment-4199099:

For encryption on the Pico W, probably: https://docs.micropython.org/en/latest/library/cryptolib.html For random ID generation on the Pico W, probably: https://github.com/pfalcon/pycopy-lib/blob/master/uuid/uuid.py

Aside: unique hardware ID generation code given at https://github.com/sparks-baird/self-driving-lab-demo/blob/1774d55d57ad4c20fb6cb9b2be7d5671446af769/src/public_mqtt_sdl_demo/main.py#L26

sgbaird commented 1 year ago

Perhaps MongoDB generates a unique API key, the Pico W uses cryptolib with a manually generated value as the encryption/decryption key to encrypt the unique device ID, and the Pico W checks to make sure this matches the expected value:

user_keys = ["abc123", "def456"]
...
my_id = my_id = hexlify(unique_id()).decode()
...
mode = 1
encrypted_device_IDs = [aes(my_id).encrypt(user_key, mode) for user_key in user_keys]
...
    ...
    assert _input_message["encrypted_device_ID"] in encrypted_device_IDs

EDIT: ah, still the same issue where the "password" is getting exposed over a public MQTT server, but the actual device ID is hidden.

On the application side, use a Python package (cryptography?) to do the encryption in the same AES mode as cryptolib.

Summary: Value Purpose
MongoDB API key database access
Unique device ID (or manually generated ID) private data to be encrypted/decrypted
user-assigned encryption key key for encrypting/decrypting device ID by client/device, respectively
sgbaird commented 1 year ago

This is harder than I thought via MicroPython. https://github.com/artem-smotrakov/micropython-rsa-signing comes with little documentation and may not be robust. There's an RSA library for CircuitPython, but I'm still hesitant to move back to CircuitPython. I could set up a web app on something like pythonanywhere, but pythonanywhere free accounts are restricted to a whitelist of websites. test.mosquitto.org has an option for certification, but it expires after 90 days and needs to be manually renewed on the website.

Related:

I'll see if I can get micropython-rsa-signing working via referencing the python-rsa instructions.

EDIT: got as far as:

from rsa.key import PrivateKey
from rsa import pkcs1

q=1640646288909657487095359180022062734411738001375224362146789767743298223
e=65537
d=3932070503866894845499545149242528634855814001291977329776170446942801119476224356319257882634777292449414158422402140416944887130847435068069262510801361
p=4287309565142106169103473694063731192260977968066421817319213385543808560298868219
n=7033958527457273924268579824377868739560691183608262863345913379771000030685205506298398376078296576269289512487456279410830335887754385753052069093874837

privkey = PrivateKey(n, e, d, p, q)

signature = pkcs1.sign("hello".encode('utf8'), privkey, 'SHA-256')

print(signature)

where the integers were generated via python-rsa, and I realized that it doesn't seem to support a PublicKey class nor an easy way to encrypt/decrypt, just to sign with the private key (?).

Tracking discussion at https://github.com/orgs/micropython/discussions/10048.

sgbaird commented 1 year ago

Seems my options are:

Will try waiting it out for now.

sgbaird commented 1 year ago

Someone got back to me on the MicroPython forums with a solution (https://github.com/dmazzella/ucrypto) that seems to be working.

Planning to leave the value of e set to what it's in the examples (65537) based on discussion at:

EDIT: related to secure handshakes https://github.com/sparks-baird/self-driving-lab-demo/discussions/127#discussioncomment-4320010 (use of HiveMQ)

sgbaird commented 1 year ago

Hardware validation now takes place by restricting the publication of documents to certain collections to specific API keys. Additionally, an encrypted, truncated device ID and a device nickname are stored in each document.

Assigning one MongoDB API key and one MongoDB collection per device is good enough. However, asymmetric encryption could still play a role at some point (hence leaving the functionality mostly there, including the storing of a truncated, encrypted device ID).