This project implements a secure, real-time Li-Fi communication channel between a Raspberry Pi Pico and a host computer. It is designed to be a robust and production-ready embedded system, featuring strong cryptography, persistent key storage, and a rich command interface for easy management.
This repository contains the embedded software for a secure Li-Fi transmitter (the Pico) and the necessary host-side components to manage it. The system is designed to showcase a secure communication workflow example, from initial key provisioning to real-time encrypted messaging.
- Sender (Raspberry Pi Pico): A powerful Li-Fi transmitter that encrypts messages using a persistent session key with AES-128-GCM. It operates autonomously and can be managed remotely via a command interface.
- Receiver/Controller (Host): A host system (like a Raspberry Pi 4 or a PC) is responsible for the initial provisioning of the session key and can be used to receive and decrypt the Li-Fi messages.
- Raspberry Pi Pico (RP2040)
- Li-Fi LED transmitter module
- USB cable (for programming and debug serial)
- Raspberry Pi 4 Model B (4 GB)
- Li-Fi receiver module
For full details on the board headers:
- Raspberry Pi Pico (RP2040) → Pico Pinout (official PDF)
- Raspberry Pi 4 (40-pin header) → Pi 4 GPIO Pinout (pinout.xyz)
| Function | Pico Pin (RP2040) | Pi 4 Header Pin | Pi 4 GPIO | Notes |
|---|---|---|---|---|
| UART1 TX | GPIO4 (Pin 6) | Pin 10 | GPIO15 RX | Pico sends → Pi 4 receives |
| UART1 RX | GPIO5 (Pin 7) | Pin 8 | GPIO14 TX | Pico receives ← Pi 4 sends |
| Ground | GND (Pin 38) | Pin 6 | GND | Common ground required |
⚠️ TX ↔ RX must cross: Pico TX → Pi RX, Pico RX ← Pi TX.
#define UART_ID uart1 // Use hardware UART1 (separate from USB debug/stdio)
#define UART_TX_PIN 4 // GPIO4 → TX line for Pico → Pi4 (sending data out)
#define UART_RX_PIN 5 // GPIO5 → RX line for Pico ← Pi4 (receiving session keys)
#define BAUD_RATE 1000000 // 1 Mbps for high-throughput, low-latency key exchange
uart_init(UART_ID, BAUD_RATE);
// Map the chosen GPIO pins to the UART hardware.
gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART);
gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART);
// Allowing the pico to retrieve the session key over UART.-
Enable UART in
/boot/config.txt:enable_uart=1 -
Reboot, then check:
ls -l /dev/serial0
It should link to
/dev/ttyAMA0or/dev/ttyS0. -
Open the port at 1 Mbps:
# Configure 1M Baud rate and interface to type messages to send on pico stty -F /dev/serial0 1000000 # setting to 1M Baud matches pico's firmware -> #define BAUD_RATE 1000000 screen /dev/serial0 1000000 # setting pico CMD interface so you can type commands like CMD: new key, CMD: print key sender, etc. directly into the Pico.
-
Authenticated Encryption:
Utilizes AES-128-GCM for state-of-the-art encryption and message authentication, protecting against both eavesdropping and tampering. -
Robust Key Persistence:
Implements a redundant A/B slot system in the Pico's flash memory to ensure the session key survives reboots and power loss. The system automatically falls back to a valid key if one slot is corrupted. -
Secure Key Provisioning:
On first boot/empty slot, listens on UART1 with preamble 0xAB 0xCD to receive the session key (e.g., from the Pi 4/auth client). Supportsnew keyandnew key -ffor controlled overwrite. -
Watchdog Timer:
The Pico (sender) is monitored by a hardware watchdog that automatically reboots the device if it becomes unresponsive, ensuring high availability. -
Secure Memory Handling:
Sensitive data like keys, nonces, and ciphertext are securely zeroed from memory after use withsecure_zero()to limit in-RAM exposure. -
Interactive Command Interface:
A rich set of commands allows for real-time management of the device, including key management, slot status checks, and diagnostics. -
Modular & Reusable Code:
The project is built with a modular architecture, separating hardware-specific logic (pico_handler), command processing (cmd_handler), and the main application logic for maximum reusability and maintainability. -
Cryptographically Secure PRNG:
mbedTLS CTR_DRBG seeded from the RP2040 ring-oscillator viapico_hardware_entropy_poll()→ high-quality randomness for salts and other needs.
- CMake ≥ 3.13
- ARM GCC Toolchain
- in deps/ for you (git submodule update --init --recursive --progress)
-
- (Pico Sender) Pico SDK
-
- (Pi4 Receiver) iotauth project for advanced key provisioning.
The code is organized into a modular structure:
src/: Core logic, including the command handler (cmd_handler.c) and Pico-specific/Pi-4 specific functions (pico_handler.c)/(pi_handler.c).-
note:pi_handler.cis under construction - waiting for TPM module
include/: Header files defining the public interface for each module.sender/src/: The main application firmware (lifi_flash.c) for the Pico transmitter.lib/: External libraries, includingmbedtls,pico-sdk, andpicotool.deps/: required repositories for session key includingsst-c-apiandiotauth.-
iotauthto be run if setting up server for live connection and live access to session key (on pi4)
CMakeLists.txt: The main build file that orchestrates the compilation of all modules and targets.
lifi-auth
├── CMakeLists.txt # Top-level CMake entry
├── README.md # This doc
├── run_build.sh # Build helper (pico/…)
├── set_build.sh # Env/preset helper
├── make_build.sh # Convenience wrapper
├── lifi_receiver.config # Default runtime config
│
├── 📁 config/
│ └── mbedtls_config.h # mbedTLS build config (Pico)
│
├── 📁 include/
│ ├── cmd_handler.h
│ ├── config_handler.h
│ ├── pico_handler.h
│ ├── protocol.h
│ └── sst_crypto_embedded.h
│
├── 📁 src/
│ ├── cmd_handler.c
│ ├── config_handler.c
│ ├── pico_handler.c
│ └── sst_crypto_embedded.c
│
├── 📁 sender/
│ ├── CMakeLists.txt
│ └── 📁 src/ # sender app sources
│
├── 📁 receiver/
│ ├── CMakeLists.txt
│ ├── 📁 include/ # receiver-local headers
│ ├── 📁 config/ # receiver config (creds, etc.)
│ ├── 📁 src/ # receiver app sources
│ └── update-credentials.sh # helper for credential files
│
├── 📁 deps/ # External project dependencies (git submodules)
│ ├── iotauth/ # (submodule) iotauth server/client bits (if used)
│ └── sst-c-api/ # (submodule) core SST C API (c_api.c, etc.)
│
├── 📁 lib/ # Third-party libraries (git submodules)
│ ├── mbedtls/ # (submodule) mbedTLS crypto (pinned)
│ ├── pico-sdk/ # (submodule) Raspberry Pi Pico SDK
│ └── picotool/ # (submodule) Pico CLI tool (building/flash)
│
├── 📁 img/
│ ├── build_artifacts_layout.PNG
│ └── physical_lifi.png
│
├── 📁 artifacts/ # (Generated) Build outputs kept for convenience
│ └── 📁 pico/
│ ├── latest.uf2 # last built firmware image
│ ├── latest.uf2.sha256 # checksum
│ └── *.json / *.uf2 # versioned build metadata & images
│
└── 📁 build/ # (Generated) CMake build trees
├── pico/ # Pico build dir (cmake/ninja/make files, libs, elf/uf2)
├── pi4/ # (optional) other target builds
└── _picotool/ # picotool helper build
deps/iotauth→https://github.com/iotauth/iotauthdeps/sst-c-api→https://github.com/iotauth/sst-c-api.gitlib/mbedtls→https://github.com/Mbed-TLS/mbedtls.git(recommended tag:mbedtls-3.5.1)lib/pico-sdk→https://github.com/raspberrypi/pico-sdk.gitlib/picotool→https://github.com/raspberrypi/picotool.git
Ubuntu / WSL
sudo apt update
sudo apt install -y build-essential cmake git pkg-config \
ninja-build \
# Pico toolchain (for `pico` builds)
gcc-arm-none-eabi libnewlib-arm-none-eabi \
# Needed to build picotool once
libusb-1.0-0-dev
# (pi4 builds will use your system gcc; if OpenSSL is missing:)
sudo apt install -y libssl-devAlso install for scripts:
sudo apt install -y libusb-1.0-0-dev pkg-config
Optional (recommended) speed-ups:
Install ninja-build to make builds faster on repeats
ssh
git clone [email protected]:asu-kim/lifi-auth.git
cd lifi-authBring in submodules recursively and show progress:
git submodule update --init --recursive --progressTo set the pico-sdk path run:
export PICO_SDK_PATH=$(pwd)/lib/pico-sdkuse helper script run_build.sh which automatically cleans and builds inside build/ folder.
- Uses
set_build.shandmake_build.sh - Creates artifacts/ for latest builds and keeps a history of most recent builds and auto-prunes old builds
- Note: first build takes longer, next builds will use .tooling to build faster
./run_build.sh pico # artifacts appear under artifacts/pico/ (latest.uf2 and versioned files)- Very first
./run_build.sh picowill take a while but after that it'll run faster (by reusing .tooling/) - ^ it will build and “install” a local picotool under
embedded/.tooling/picotool/and wire CMake to use it automatically. - Subsequent runs:
-
- reuse .tooling/ (no warning spam, no re-build).
-
- cleans the build so no need to
rm -rf build.
- cleans the build so no need to
-
- cleans the
artifacts/pico(or pi4) folder based on how manyKEEP_BUILDSare set
- cleans the
./run_build.sh pi4 # artifacts appear under artifacts/pi4/ (latest and versioned files)cd deps/iotauth/examples
./cleanAll.sh
./generateAll.shYou will be prompted to enter your password. Build the server:
cd ../auth/auth-server/
mvn clean installbuild the server:
java -jar target/auth-server-jar-with-dependencies.jar -p ../properties/exampleAuth101.propertiesEnter your password again. Now you should leave that terminal running, open another terminal
- Note: for latest build instructions check: https://github.com/iotauth/iotauth
in the second terminal navigate to root directory of the repo:
lifi-auth/We can now connect to the server with pi4 (assuming you built it above with./run_build.sh pi4):
- once inside
lifi-auth/runupdate_credentials.shfrom there
./receiver/update-credentials.shWith this lifi-auth/receiver/config/credentials should now be populated with the necessary Auth101EntityCert.pem & Net1.ClientKey.pem files
ls receiver/config/credentials/Note: never push these files to Github
- Now connect to the server using the latest build (will always be here and updated from running
./run_build pi4previously). - iotauth takes a config to connect: so lifi_receiver.config will be the second argument:
./artifacts/pi4/latest lifi_receiver.configThis will now connect to auth you will get "Retrieving session key from SST..." The pi4 should print the session key (for debug).
- Pico:
artifacts/pico/latest.uf2(+latest.uf2.sha256,latest.json) - Pi4:
artifacts/pi4/latest(executable) (+latest.sha256,latest.json)
We keep a short history of prior builds next to latest*, and prune to the last 3 build sets by default. Override per run:
KEEP_BUILDS=5 ./run_build.sh pi4( cd artifacts/pi4 && sha256sum -c latest.sha256 )
( cd artifacts/pico && sha256sum -c latest.uf2.sha256 )build/*is throwaway; safe to delete any time..tooling/picotoolis the reusable local install that suppresses SDK warnings.artifacts/<target (pi4 OR pico)>/latest*always points to the newest build; older builds are pruned.
-
Could NOT find OpenSSL (missing: OPENSSL_CRYPTO_LIBRARY)(pi4):sudo apt install -y libssl-dev -
No installed picotool with version …(pico): Our script builds a local picotool once; ensurelibusb-1.0-0-devis installed, then re-run./run_build pico. -
pico_sdk_init.cmake not foundor missing SDK:git submodule update --init --recursive --progress(we vendored the SDK underembedded/lib/pico-sdk). -
Assembler errors like
.syntax/.cpuduring Pico builds: You’re using the host gcc instead of ARM GCC. Installgcc-arm-none-eabi libnewlib-arm-none-eabi, or setPICO_TOOLCHAIN_PATHto your ARM toolchain root.
After ./run_build.sh pico, your firmware is here: artifacts/pico/latest.uf2.
./run_build.sh picoOption A (TESTED/WORKING) — Windows & WSL (usbipd Powershell)
- Unplug the Pico. Hold BOOTSEL while plugging it in (it mounts as a USB drive).
- Copy or drag the UF2:
- I did this using windows->WSL running Windows PowerShell as admin via usbipd in this order:
-
- Flashed latest.uf2 to pico
-
- Attached the busid to wsl
usbipd listIn the same row as USB Mass Storage Device, RP2 Boot -> Note the hardware under the BUSID: use that id for this command. In my case the BUSID is 2-2.
- Replace
2-2with your BUSID --found when running usbipd list:
usbipd attach --busid 2-2 --wslIn WSL now you should be able to see ls /dev/ttyACM0
ls /dev/ttyACM*If thats the case you can run:
picocom /dev/ttyACM0
Now you can provision pico with keys (up to 2) and use CMD: help for all commands.
These keys will be saved to flash storage even if it powers off it will reboot with valid key
- Note: logic to valid keys are not implemented yet (timers with rel_validity and abs_validity in sst-c-api -- SOON --)
Option B — Flash to Pico
UF2="artifacts/pico/latest.uf2"
DEST="$(ls -d /media/$USER/RPI-RP2 /run/media/$USER/$USER/RPI-RP2 2>/dev/null | head -n1)"
cp "$UF2" "$DEST"/Adjust
DESTif your system mounts the drive somewhere else (e.g.,/media/user/RPI-RP2). On Windows, just draglatest.uf2onto theRPI-RP2drive in Explorer.
This guide outlines the steps to provision the Pico with a session key from the host and establish secure communication.
- All hardware is connected, especially the Pico ↔ Pi 4 UART connection (see wiring details).
- The firmware and executables have been built successfully using
./run_build.sh. - The
lifi_receiver.configfile is present in theembedded/directory and all paths are correct.
Next, power on the Pico. If it's the first time running or its key slots are empty, it will automatically enter provisioning mode.
- Flash the firmware (
artifacts/pico/latest.uf2) to the Pico. If it's already flashed, simply reset it or unplug and plug it back in. - Connect to the Pico's USB serial port with a terminal emulator (e.g.,
screen,minicom, PuTTY) to monitor its status. - On boot, the Pico will fail to find a key and print:
No valid session key found. Waiting for one... - The Receiver, which was already waiting, will send the key. The Pico will receive it, save it to flash, and confirm with:
Received session key: <hex> Key saved to flash slot A.
The Pico is now provisioned and ready to communicate securely.
Once provisioned, the system is ready for use.
- In the Pico's USB serial terminal, type any message (e.g.,
hello world) and press Enter. - The Pico will encrypt the message and send it over the Li-Fi UART.
- The Receiver's terminal will display the decrypted message:
Decrypted: hello world
You can manage the session key from the Pico's serial console.
- Request a New Key from SST: To have the receiver fetch and send a new session key, simply type
new keyornew key -f(force) as a message in the Pico's terminal. The receiver will handle the request and provision the Pico with the new key.
Use the on-device command interface over the Pico’s USB serial:
-
Check key status:
CMD: slot status -
(Other commands; see the "Command Interface" section below for more.)
- Nothing prints on USB: Ensure your terminal is on the Pico’s USB CDC port and baud is 115200. Unplug/replug while holding BOOTSEL to reflash if needed.
- Key not accepted: Make sure you sent exactly 2 bytes of preamble (
AB CD) + 32 bytes of key (not ASCII text unless your firmware converts it). - Receiver can’t decrypt: Double-check both sides use the same 32-byte key and the receiver was restarted/reloaded after provisioning.
Interact with the Pico over the USB serial connection. All commands are prefixed with CMD:.
| Command | Description |
|---|---|
help |
Displays a list of all available commands. |
print key |
Prints the currently active session key. |
slot status |
Shows the validity of key slots A and B and which one is active. |
use slot A / use slot B |
Switches the active session key to the one in the specified slot. |
clear slot A / clear slot B |
Erases the key from the specified slot. |
new key |
Waits to receive a new key, but only if the current slot is empty. |
new key -f |
Forcibly overwrites the key in the current slot. |
print slot key A / B / * |
Prints the key stored in a specific slot (or all slots). |
entropy test |
Prints a sample of random data from the hardware RNG for verification. |
reboot |
Reboots the Pico. |
- The system is designed for high reliability, automatically recovering from reboots and provisioning itself on first run.
- Future work could include:
- A GUI-based host application for managing multiple devices.
- Support for secure different file types over Li-Fi.
- Integration with a hardware Trusted Platform Module (TPM) on the host for even more secure key storage.