This is a personal project that creates MFA TOTP codes. I created it to help me to get TOTPs without interacting with my phone. Before creating it, every time I needed a new TOTP, I had to:
- find my phone
- if it is not charged, charge it, and then wait a few minutes until it is fully operational
- if it is charged, unlock it
- find a Virtual MFA App
- unlock it
- find the TOTP for the service I need, which sometimes requires a lot of vertical scrolling.
Other motives:
- when using my personal phone to get mfa totp codes, I get distracted by notifications and a lot of other things.
- not safe to store secrets on a device that you use when going out. If the device is lost, and there is no security layer to protect unintended access to the virtual MFA App, there is a high chance someone can impersonate your identity.
- secrets are decoupled from the device, and this allows me to easily switch to another one when necessary. I just need to insert the SD card containing my services onto another device flashed with the same code.
- not all services that I use support fido keys or passkeys.
- I wanted to learn how MFA works.
- I wanted to learn how ESP32 works.
demo_2.mp4
demo_3.mp4
demo_v0.5.0.mp4
demo_v0.5.0-2.mp4
ESP32-2432S028
- 3D printed black case
- Acrillic case
INFO: you can buy this board from Aliexpress clicking on any of these affiliate links: USD BRL
INFO: you can buy this acrillic case from Aliexpress clicking on any of these affiliate links: USD BRL
INFO the 3D model for the black case was taking from this link
Part | Cost |
---|---|
ESP32-2432S028 | 9.25 USD |
3D printed black case | 12.7 USD |
Acrillic case | 2.5 USD |
INFO: all prices do not consider expenses with taxes and shipping.
INFO: all prices were taking in February 2024.
dependency | version |
---|---|
python | >= v3.9 |
node | >= v18.18 |
pnpm | >= v8.15 |
vscode | >= v1.87 |
platform.io ide vscode extension | >= v3.3 |
docker | >= v25.0 |
WARNING: don't forget to install a driver to allow your OS to recognize esp32
INFO: Node.js is required because it runs several development tools used in this project. Among these tools are those that enforce the "conventional commits" standard. This standard is a lightweight convention on top of commit messages, offering an easy set of rules for creating an explicit commit history.
INFO: if platform.io extension does not recognize your board after clicking on
Upload
,Upload and Monitor
orMonitor
buttons, it means the driver was not properly setup. In Macos, after installing the driver from Sillicon Labs, I had to restart the system before mac could identify the board.
- 2.4Ghz WiFi signal with internet connection, in order to sync the board's clock with the NTP server.
- SD card. Once the board has finished its setup, you can remove the SD card, and store somewhere safe.
- Install Platform IO IDE extension in VS Code.
- Open this project in a new vscode workspace, and wait for Platform.IO to finish its automatic setup.
- Open
platformio.ini
and edit - Connect your board to your computer. If you installed the proper drivers, the next steps should work just fine.
- Click on the Platform.IO button, in VSCode's sidebar.
- Then click on
esp32-cyd -> General -> Build
and wait until the build is done. - Finally, click on
esp32-cyd -> General -> Upload ad Monitor
to flash the code into your board.
Alternatively, if you prefer using clis, install PlatformIO's CLI using this tutorial, and then follow the next steps:
- run
platformio device list
and annotate the device path of your board.
INFO: You can discover which path belongs to your board by comparing the outputs of this command when your board is connected and not.
- setup environment variables
export WIFI_SSID="WIFI PASSWORD"
export WIFI_PASSWORD="WIFI PASSWORD"
export PIN_HASH="HMAC SHA256 PASSWORD HASH HEX STRING"
export PIN_KEY="PASSWORD HASH KEY"
export MQTT_PORT="1883"
export MQTT_SERVER="192.168.0.1"
export MQTT_USERNAME="test"
export MQTT_PASSWORD="test"
INFO: Wi-Fi variables are required because this project uses the NTP server to set its time.
INFO: Pin variables are optional. If both are set, the pin number screen is shown before your TOTP Codes can be displayed.
WARNING: remember to use a network which has access to the internet, and is isolated from your main network.
WARNING: platform.io vscode extension tasks (build, upload, monitor...) are not using env variables. Therefore, you must open platformio.ini and set
-D WIFI_SSID
and-D WIFI_PASSWORD
with your values.
INFO: use this app to hash your password. Its hashed output is the value you have to load
PIN_HASH
env variable. Remember to setPIN_KEY
to the same secret you used to hash your password.
INFO:
MQTT
env variables are optional. To enable the board to receive secrets from an mqtt broker,MQTT_SERVER
andMQTT_PORT
must be specified.
- run
platformio run --environment esp32-cyd
to build the application - upload the code to your board using
platformio run --target upload --upload-port ${DEVICE_PATH} --target monitor --environment esp32-cyd
.
WARNING: Remember to substitue
${DEVICE_PATH}
with the value you got in step 1.
The secrets used to compute TOTP codes must be stored in a file called keys.txt
, and be placed in the root of an SD card. It must follow the format shown below:
service_id,encoded_base_32_secret
Each service must be added on a new line. For example:
aws-1,DSAJDHHAHASAUDOASNOTREALOADAKLDASAJFPOAIDONTEVENTRYOASFAIPO
aws-2,DSAJDHHAHASAUDOASNOTREALOADAKLDASAJFPOAIDONTEVENTRYOASFAIPO
aws-3,DSAJDHHAHASAUDOASNOTREALOADAKLDASAJFPOAIDONTEVENTRYOASFAIPO
WARNING: file must end with a new line.
WARNING: secrets must be unencrypted and based 32 encoded. In the future, my plan is to decrypt secrets using a secret stored in the board. With this approach, a stolen SD card won't work on a different board flashed with the same code unless the board has the same key.
- Go to https://totp.danhersam.com/
- Paste/type your encoded base 32 secret in the secret field, and then compare the TOTP code shown with the one you are seeing on the ESP32's screen.
To enable saving secrets to ESP32 via a local network, this project uses MQTT as the messaging protocol, Node-red as the postman (per say) and Eclipse Mosquito as the MQTT broker. Both services can be started using docker compose using a docker-compose.yaml file located in the root of this project, in order to ease the setup. So, before continuing, install Docker on your computer following the guide found here.
After that, run the following script to start both node-red and the mqtt broker:
./scripts/start-node-red.sh
Open node-red at http://localhost:1880
, and then load the flow from ./node-red/insert-secret.json
. For now you must manually setup the node input with the secret you want to send to your ESP32, but in the future I plan to have a small app and a chrome extension as clients using the local node-red services to ease the process of storing secrets on the board.
WARNING Make sure to have the following ports free before running
./scripts/start-node-red.sh
: 1880 (node-red), 1883 (eclipse/mosquitto), 9001 (eclipse/mosquito).
You should see the following containers in the docker app.
WARNING remember to assign static ips to the host running the MQTT service, as weel as for the esp32, in your router. This is a suggestion to avoid having to update the
MQTT_SERVER
constant with a new ip every time your router decides to change the ip of your host.
WARNING if your host can't receive messages from other devices on the same network, it could be a firewall problem. Configure the firewall in the host to enable it to receive requests from other devices on your local network.
After all services have initialized, open node-red at localhost:1880
, and import ./node-red/insert-secret.json
flow. Use this tutorial to guide you to import flows into node-red.
WARNING remember to put the SD card again in the board, if you want the secret to be stored. If you don't do it, after a reset the TOTP code for that secret won't appear because the secret wasn't written to disk.
People often use multiple services that require MFA TOTP codes with high frequency because of their short living sessions.
It is not secure to have unencrypted secrets stored without protection
Ease the process of adding new services. With this feature I won't need to insert the SD card on my computer. If there is no SD card on the board, the channel to register new services is going to be closed. I also plan to require fingerprint/pin/password before opening this channel.
When the ESP32-MFA-Authenticator extension is enabled, a new button called "register secret" appears, in the browser's context menu, when right clicking over a QR code. When selecting this button, the registration flow starts.
1. User select "register secret" to start the registration flow.
2. The ESP32-MFA-Authenticator extension receives it, and forwards the message, using an HTTPS, to the ESP32-MFA-Authenticator service, built with keycloak.
3. The ESP32-MFA-Authenticator service verifies if the User is authenticated. If the request doesn't have the User's credentials, the Authentication flow starts.
4. After finishing the authentication flow, the registration flow begin.
5. The first step is to connect to the right MQTT topic using username and password. These credentials are stored securely in the server. If a successful connection is opened, the registration flow continues.
6. The ESP32-MFA-Authenticator service creates the message that contains the secret name and its value, then signs it with a strong key, and finally posts the message to the MQTT topic the esp32 board can read.
7. The esp32 then validates if the message came from the ESP32-MFA-Authenticator service. If the message is valid, the flow continues.
8. The esp32 then reads the secret and stores it in the SD Card, if one is present.
9. The esp32 then notifies the ESP32-MFA-Authenticator service if everything went well or not.
10.The ESP32-MFA-Authenticator service notifies the ESP32-MFA-Authenticator extension about the result.
INFO: the above steps are summarizing my initial plan.
I work with a customer that has multiple AWS accounts, and each has its own MFA Virtual device. To help me to easily find the MFA TOTP codes for a group of accounts that belongs to the same customer, I decided to create a way to group TOTP codes together on its own separate view. Each group of TOTP secrets will result in a page on the TOTP Screen. The User can select the page by swiping to the right or left. With this feature, Users will be able to manage multiple virtual MFA devices for multiple customers using the same device. To further secure TOTPs for a group, the User will be able to set a PIN code for a group. If PIN code is set for that group, a PIN Screen appears before the group of TOTPs can be rendered. There will also still exist the Global PIN code, which protects all TOTPs.
Improve the validation function to block access to the board after few wrong attempts happened. With this enhancement, brute forcing all possible combinations won't be possible.
Instead of typing a pin code, it will be possible to unlock the board using a fingerprint. The goal is to ease the access to the TOTP codes, while maintaining them secure. It will also work globally or by group.
Users will be able to create custom styles for the UI standard components