Table of Contents
- Part 1 — Introduction to PicoKeys
-
Part 2 — Compiling PicoKeys Firmware
- Compiling the Firmware Without Secure Boot
- Installing Dependencies and ESP-IDF
- Downloading and Compiling Pico FIDO
- Downloading and Compiling Pico HSM
- Flashing the Firmware Without Secure Boot
- Compiling the Firmware with Secure Boot
- Do I Need to Enable Flash Encryption?
- Generating the Signing Keys
- Associating the Key Digest with PicoKeys Firmware
- Configuring and Signing Secure Boot for FIDO and HSM
- Generating Signed FIDO and HSM Firmware
- Flashing the Secure Boot Image for FIDO and HSM
-
Part 3 — Secure Boot and Flash Encryption
- Compiling the Firmware with Secure Boot and Flash Encryption
- Cloning the Projects and Installing ESP-IDF 5.5.1
- Required Adjustments for HSM and FIDO
- Generating the Keys
- Associating the Key Digest with PicoKeys Firmware
- Configuring Secure Boot and Flash Encryption
- Generating and Validating the Signed FIDO and HSM Firmware
- Preparing the Board and FE Key — First Firmware Only
- Flashing the First Firmware onto the Board
- Flashing New Encrypted Firmware — Second or Later Flash
- Sources
Part 1 — Introduction to PicoKeys
My Recommendation
Introduction to PicoKeys
PicoKey is an open-source project that turns microcontrollers (including some RP2040- and ESP32-S3-based boards) into security devices similar to physical authentication keys, such as the YubiKey.
Depending on the firmware used, the device can provide features such as FIDO2, HSM, and OpenPGP & PIV.
The idea is to provide a key similar to a YubiKey: instead of storing certain credentials or private keys directly on the computer, they can be protected inside the physical device. The main difference is that PicoKey is an open-source project.
Because PicoKey uses generic hardware and open-source firmware, it offers plenty of flexibility, but it also requires care. Unlike a YubiKey, whose hardware, firmware, and manufacturing chain are controlled by the manufacturer, PicoKey's security also depends in part on the microcontroller used, the firmware configuration, and the protections enabled on the chip itself.
Community vs Enterprise
PicoKey has two main licensing options: Community and Enterprise. The Community version is free and distributed under the AGPLv3 license. It is mainly intended for personal use, studies, labs, testing, and prototype development.
We can download the firmware, study the source code, modify it, and use it normally. However, there's a catch: because of the AGPLv3 license, certain obligations apply if the firmware is modified and redistributed or used as part of certain network services.
The Enterprise version uses a proprietary commercial license and is aimed at production environments, businesses, manufacturers, and deployments involving multiple users or devices. Apart from the licensing difference, Enterprise customers may receive additional features.
For personal use of PicoKey as FIDO2, HSM, or OpenPGP, the Community version is normally sufficient.
PicoKey App
The PicoKey App is the official application for configuring, managing, and updating devices in the PicoKeys ecosystem, including Pico FIDO, Pico HSM, and Pico OpenPGP. It lets us view device information, update firmware, configure board-specific settings, manage credentials, and enable security features such as Secure Boot and Secure Lock.
The app is also responsible for a process called commissioning, which essentially adapts a generic firmware image to the specific hardware on which it is running. This is necessary because the same firmware can run on several different boards, but some characteristics depend on the hardware used, such as:
- GPIO used by the button;
- LED and indicator behavior;
- board identification;
- USB VID/PID;
- device-specific settings;
- certain security settings.
The documentation explains that this information cannot be set generically during firmware compilation and must be configured later on the device.
Among the PicoKey App features are:
- update firmware;
- configure VID/PID;
- configure LED and button;
- manage passkeys;
- manage OATH and OTP slots;
- manage Pico HSM keys;
- configure PIN and SO-PIN;
- configure Secure Boot;
- configure Secure Lock;
- perform a factory reset;
- manage some OpenPGP and PIV features.
The firmware itself works independently of the app, since its cryptographic features are provided by the firmware rather than the PicoKey App. Even without the app, we'll be able to use FIDO2, WebAuthn, PIC, OpenPGP, HSM, SSH, and so on.
Unfortunately, the problem arises during initial setup and when managing certain hardware-specific features. In newer versions, the app has become the tool for performing commissioning (as mentioned above) and configuring board-specific parameters.
Therefore, for normal PicoKey use in a lab or for simpler personal projects, you won't need the app as much. There is also an alternative for more "advanced" users: you can compile the firmware manually, including the parameters specific to the board you're using.
The developer himself confirmed that PicoKey can be configured without the app. This means the PicoKey App is not technically required for PicoKey to work, but we lose several of its useful features, such as firmware updates and enabling Secure Boot and/or Secure Lock.
Flashing the Firmware
The PicoKeys firmware can be installed using different methods, depending on the controller. The easiest way is to use a ready-made image provided by the project and flash it directly through the browser, or compile the firmware manually.
Earlier, when talking about the app, I mentioned commissioning, but it's worth emphasizing that installing the firmware and performing commissioning are different things. Installing the firmware means flashing Pico FIDO, Pico HSM, or Pico OpenPGP onto the controller.
Commissioning, on the other hand, configures and customizes device characteristics such as VID/PID, USB identification, LED/button, some security options, and other settings specific to the controller being used.
Flashing Through the Browser — ESP32-S2/S3
Boards based on the ESP32-S2 or ESP32-S3 can be flashed directly through a browser. With this method, we don't need to install esptool or ESP-IDF, or download the image manually. The browser communicates directly with the ESP32 bootloader and flashes the selected firmware.
To put an ESP32-S3 into flashing mode, follow these steps:
- Hold the BOOT button;
- Press the RESET button;
- Release the RESET button;
- Release the BOOT button.
Then open ESP32 Flasher, select the firmware you want, and click Connect.
After flashing, the ESP32 reboots and runs the PicoKeys firmware.
VID/PID
Installing the firmware as explained above uses the parameters provided by the image distributed by the project. However, some third-party tools may not recognize these identifiers. The ESP32 Flasher itself recommends configuring VID/PID to improve compatibility with tools such as OpenSC, PKCS#11, ykman, gpg, and others.
The VID (Vendor ID) identifies the USB device manufacturer, while the PID (Product ID) identifies a product associated with that manufacturer. Some applications use these identifiers to find compatible devices, while others can identify them directly from the protocols they implement.
This means that changing the VID/PID is not required to use every firmware feature, but it may be necessary for certain tools to recognize the device. For example, Pico FIDO uses HID, a USB interface commonly supported by operating systems and browsers. This allows the device to work as a FIDO2/WebAuthn authenticator without relying on the PicoKey App or on another manufacturer's specific VID/PID.
Pico HSM uses CCID, an interface for communicating with smart card devices. In this case, recognition may depend on the identifiers supported by the driver or application being used.
The official documentation describes several ways to change USB identifiers without relying on the PicoKey App, which is the easiest method for changing them, although I've never used it myself.
PicoKey App
The PicoKey App lets you select predefined identifiers or enter a VID/PID manually. The settings are applied during commissioning, without recompiling the firmware. However, as we've already seen, this requires paying for the app.
Pico Patcher
They also provide Pico Patcher, a free tool that lets you change the identifiers directly in your browser, but it only works with Raspberry Pi boards. The procedure is as follows:
- Visit the Pico Patcher page.
- Select a firmware file in
.uf2format. - Choose one of the available manufacturers or enter a custom VID/PID.
- Click
Patchto generate a new firmware image. - Flash the modified image onto the device.
Available presets include YubiKey, Nitrokey, GnuPG, and FSIJ. All processing takes place locally in the browser, so the modified firmware is not uploaded to the project's servers.
Dynamic Changes Through Pico HSM
Pico HSM also offers a command-line alternative through the pico-hsm-tool.py tool. According to the official documentation, you can change the VID/PID dynamically using:
./pico-hsm-tool.py phy vidpid 1234:5678
The values 1234:5678 are examples and should be replaced with the desired identifiers. After successfully running the command, you need to restart the device for the new identifiers to take effect. This method lets you change VID/PID without creating a new firmware image.
Manual Compilation
Another method is to configure the USB identifiers directly in the source code or firmware build parameters. The Pico HSM and Pico FIDO repositories document options such as USB_VID, USB_PID, and VIDPID for builds targeting Raspberry Pi Pico boards.
On ESP32-S3, you need to use -DVIDPID=XXX to specify the profile you want to apply. The table below lists the available profile names.
| Group | Profile | VID:PID |
|---|---|---|
| HSM | NitroHSM |
20A0:4230 |
| HSM | YubiHSM |
1050:0030 |
| FIDO | NitroFIDO2 |
20A0:42B1 |
| FIDO | Nitro3 |
20A0:42B2 |
| FIDO | Yubikey5 |
1050:0407 |
| FIDO | YubikeyNeo |
1050:0116 |
| PGP/OpenPGP | NitroStart |
20A0:4211 |
| PGP/OpenPGP | NitroPro |
20A0:4108 |
| PGP/OpenPGP | Gnuk |
234B:0000 |
| PGP/OpenPGP | GnuPG |
1209:2440 |
Using an ESP32-S3
I bought a XIAO ESP32-S3 because the PicoKeys project itself recommends it, especially for security reasons. The main advantage of this board is that it has security features implemented directly in hardware, improving protection for both the firmware and the keys stored on it.
One of its main features is Secure Boot v2. It cryptographically verifies the bootloader and firmware before executing them. Once configured, an attacker cannot simply flash modified firmware onto the device to try to extract its keys. Only firmware signed with an authorized key will be executed.
Another important feature of this board is Flash Encryption. It has two operating modes. Under the default configuration, the ESP32-S3 generates an XTS-AES key during its first boot and stores it in the eFuses (this mode is known as Development), protected against reading. The problem is that, in Release mode, a different method is required to flash new firmware: it must be signed and then encrypted with the board's key before flashing.
You can also generate the Flash Encryption key on your computer, write it to the eFuses before enabling encryption, and keep it on the computer to prepare firmware before flashing (this mode is known as Release). The advantage is that we can keep flashing firmware using ROM Download Mode, but we must send an already encrypted image. To do that, we need to use --no-stub in Secure Download Mode.
Because Secure Boot also uses eFuses, it's worth clarifying that it uses a different area from Flash Encryption. With Secure Boot, the eFuses store a reference to the key used to verify firmware signatures. With Flash Encryption, they store the key used to encrypt flash memory data.
When PicoKey uses an eFuse area to store cryptographic data, that area is write-protected to prevent changes. Also, eFuses are permanent: once programmed, their contents cannot simply be deleted or restored as they can in flash memory.
Another useful feature is Secure Lock. It programs permanent locks into eFuses to prevent changes to certain security settings. In theory, that is what Secure Lock does, but in my tests, several protections were applied simply by enabling Secure Boot, including many associated with Secure Lock. JTAG, for example, was permanently disabled, and ROM Download entered Secure Download Mode, which restricts available commands. Therefore, even without applying Secure Lock, these protections remain active after enabling Secure Boot. Secure Lock does a few other things, but I see those additional protections as desirable rather than essential; not having them doesn't invalidate using the device.
The ESP32-S3 also has hardware cryptographic accelerators for AES and SHA, along with HMAC, RSA Digital Signature (RSA_DS), and RNG peripherals. The HMAC peripheral can use a key stored in eFuses without exposing it to software. RSA_DS can also use HMAC to protect RSA private key parameters and perform signatures without exposing the key to firmware.
In Pico HSM, the SDK also enables the internal SAR ADC entropy source and uses the hardware RNG to obtain random data. Physical noise from the chip is mixed into the RNG, helping generate keys and other random values. This source is internal to the ESP32-S3, so there's no need to connect an antenna or any other component.
With this entropy source enabled, the ESP32-S3 hardware RNG uses physical noise from the SAR ADC to generate truly random numbers instead of relying only on a pseudorandom sequence. The SAR ADC stands for Successive Approximation Register Analog-to-Digital Converter. It is a converter that measures an analog voltage and turns it into a digital value. On the ESP32-S3, the chip's natural electrical noise slightly changes these measurements. With the entropy source enabled, the hardware RNG uses and mixes these variations to produce random numbers. The ADC does not generate the numbers on its own; it provides a physical entropy source for the RNG.
That's what I understood about the SAR ADC, but I should make it clear that this is outside my area of study, so I'm sharing my understanding 😅.
A Microcontroller Is Not a Secure Element
Flashing FIDO2 Through the Browser
I'll do everything online using the browser-based tools provided by the project. The official project recommends using ESP32 Flasher online. The official installer is available at https://www.picokeys.com/esp32-flasher/.
Before flashing the firmware, we need to put the XIAO ESP32-S3 into the boot mode used for programming. PicoKeys calls this DFU Mode. With the board connected to the computer, do the following:
- Press and hold the BOOT (B) button;
- Press and release the RESET (R) button;
- Release the BOOT (B) button.
The board will enter a mode where it waits to be flashed. With the ESP32-S3 in DFU Mode, open the official installer at https://www.picokeys.com/esp32-flasher/.
- Select Pico Fido;
- Click Connect;
- Select the device corresponding to the ESP32-S3;
- Confirm the connection;
- Start flashing;
- Wait until the process is completely finished before disconnecting the board.
The website itself downloads and flashes the firmware required by the ESP32-S3. After flashing, disconnect and reconnect the board. It will boot with Pico FIDO.
Now let's check whether Linux specifically recognizes the FIDO2 interface.
# Install the tools:
sudo apt update && sudo apt install -y fido2-tools
# List the devices:
$ fido2-token -L
/dev/hidraw17: vendor=0x2e8a, product=0x10fe (Pol Henarejos Pico Key)
# List our FIDO device's capabilities:
$ fido2-token -I /dev/hidraw17
proto: 0x02
major: 0x08
minor: 0x00
build: 0x00
caps: 0x05 (wink, cbor, msg)
version strings: U2F_V2, FIDO_2_0, FIDO_2_1, FIDO_2_2, FIDO_2_3
extension strings: uvm, credBlob, credProtect, hmac-secret, largeBlobKey, minPinLength, hmac-secret-mc, thirdPartyPayment
algorithms: es256 (public-key), es384 (public-key), unknown (public-key)
aaguid: 89fb94b706c936739b7e30526d968145
options: rk, noalwaysUv, credMgmt, authnrCfg, noclientPin, largeBlobs, perCredMgmtRO, pinUvAuthToken, setMinPINLength, nomakeCredUvNotRqd
fwversion: 0x800
maxmsgsiz: 1024
maxcredcntlst: 16
maxcredlen: 1024
maxlargeblob: 2048
maxrpids in minpinlen: 120
minpinlen: 4
pin protocols: 1, 2
pin retries: 8
pin change required: false
uv retries: undefined
Configuring a PIN
We already discussed PINs when I explained the YubiKey, specifically FIDO authentication. It can require only the physical presence of the key and a touch, or the key plus additional user verification such as a PIN or biometrics.
Basically, the PIN confirms that the person using the physical key is authorized to access certain functions, such as passkeys stored on the device and management operations.
A PIN is optional for some operations, so you can use Pico FIDO without one. However, some services and features may require user verification, making a PIN necessary.
To configure a PIN, we can use fido2-token:
fido2-token -S /dev/hidraw17
Another way to configure the PIN is through the PicoKey App. The app lets you set the PIN and adjust related policies, such as minimum length and complexity.
To change the PIN:
fido2-token -C /dev/hidraw17
To remove the PIN, you need to reset the authenticator. Be careful, though: a reset does not delete just the PIN. It also deletes credentials/passkeys stored on the device and restores the FIDO state.
fido2-token -R /dev/hidraw17
Listing and Deleting Configured Passkeys
We can list resident passkeys stored on the key, but only those that are discoverable. We can also delete them individually using fido2-token. A discoverable credential is a FIDO2 credential stored on the physical key that the key can find without the website first specifying which credential to use. This enables passkey/passwordless scenarios, in which you can sign in without first entering a username and password.
A non-discoverable credential works differently. The private key is still protected inside the physical device, but the device doesn't keep a searchable entry for that login. In this case, the server must send a Credential ID to specify which credential to use.
A discoverable passkey needs to keep a persistent record inside the authenticator with enough data to find that account later. The problem is that this consumes the key's internal storage, so it's always a good idea to delete credentials you no longer use.
A non-discoverable credential usually takes up much less, or almost no, space on the key. This is because the server stores the Credential ID and sends it back during login. The key derives or retrieves the necessary material from that identifier without having to store one entry per website.
# Check which /dev/hidrawX belongs to the key:
$ fido2-token -L
/dev/hidraw3: vendor=0x2e8a, product=0x10fe (Pol Henarejos Pico Key)
# List the registered discoverable passkeys:
$ sudo fido2-token -L -r /dev/hidraw3
Enter PIN for /dev/hidraw3:
00: asdasdasdasdafU3lMtcdsffdsdHLsdfsffsOOgd459= teste.com
# To delete one, the command should be:
$ sudo fido2-token -D -i 'asdasdasdasdafU3lMtcdsffdsdHLsdfsffsOOgd459=' /dev/hidraw3
The table below summarizes what we can do with the fido2-token command:
| App Feature | How |
|---|---|
| Detect Pico FIDO | fido2-token -L |
| View capabilities/AAGUID | fido2-token -I <device> |
| Set PIN | fido2-token -S <device> |
| Change PIN | fido2-token -C <device> |
| Reset FIDO | fido2-token -R <device> |
| List passkeys |
fido2-token -L -r <device> + -L -k
|
| View passkey metadata | fido2-token -I -c <device> |
| Delete passkeys | fido2-token -D -i ... |
| Change credential name/display name | fido2-token -S -c ... |
Read/write/delete largeBlob
|
fido2-token -G/-S/-D -b ... |
Enable Always UV
|
fido2-token -S -u |
Disable Always UV
|
fido2-token -D -u |
| Set minimum PIN length | fido2-token -S -l N |
| Create credentials manually | fido2-cred |
| Test authentication/assertion | fido2-assert |
↑ Back to the Table of Contents
Part 2 — Compiling PicoKeys Firmware
Compiling the Firmware Without Secure Boot
First, I'll show how to compile without enabling Secure Boot, since this feature is irreversible. This way, you can test and use the board, and reflash it more easily if needed. Use a board as a lab device: test and learn before taking irreversible steps. Once everything is working and you feel comfortable and confident, we'll enable the security features.
I'll continue using the XIAO ESP32-S3 for the procedures described here. I want to clarify that I won't specify a tag when cloning the repositories, since the installation procedure has changed and new improvements have been added to each project's main branch. Below are the branches I used for each project:
### FIDO
Hash: ade770281dd4fd8c627add34ed078f56ec40a07b
tag: nightly-main and nightly-development
### HSM
Hash: 5d6a32c81b2367fb83e5320400f61217d2bd0d2f
tag: nightly-master and nightly-development
I submitted two fixes for security issues in the FIDO project, so they may soon be merged into the main project. In that case, you will still be able to use the hashes above, but you'll need to apply the fixes manually until they are incorporated.
I just want you to understand that using the main branch changes the way the firmware is compiled, since new features have been added and the build process is even easier. That's why I didn't specify a tag when cloning the repositories.
Installing Dependencies and ESP-IDF
I'll install the compilation tools used by ESP-IDF on a virtual machine running Ubuntu 26.04.1:
sudo apt update
sudo apt install -y git wget flex bison gperf python3 python3-pip \
python3-venv cmake ninja-build ccache libffi-dev libssl-dev \
dfu-util libusb-1.0-0 openssl
Let's create a directory to store and install the ESP-IDF source code:
mkdir -p ~/src/picokeys
cd ~/src/picokeys
Because ESP-IDF uses Git submodules for its components and dependencies, we need to clone with --recursive to download them. Otherwise, we'd have the main repository but not the complete source code required to compile the firmware. I'll also download tag v5.5.1 specifically, corresponding to version 5.5.1 of the code. Even though this is the latest available version as I write this tutorial, I want to make it very clear which version I'm using so it doesn't get confused with whatever version you might be running.
Another reason for using v5.5.1 specifically is that the Pico FIDO project's build script uses that exact version. It runs git checkout tags/v5.5.1 before installing the tools and compiling.
To find out which version to use, check these files:
- For HSM: https://github.com/polhenarejos/pico-hsm/blob/master/workflows/autobuild.sh
- For FIDO: https://github.com/polhenarejos/pico-fido/blob/main/workflows/autobuild.sh
Search for tags/v, and you'll see the following:
elif [[ $1 == "esp32" ]]; then
sudo apt install -y git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0
git clone --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
git checkout tags/v5.5.1
./install.sh esp32s3
. ./export.sh
The key line for identifying the version is git checkout tags/v5.5.1. So let's clone ESP-IDF at exactly version 5.5.1:
git clone --branch v5.5.1 --recursive https://github.com/espressif/esp-idf.git esp-idf && cd esp-idf
Now let's install the tools needed to develop and compile firmware for the ESP32-S3:
unset IDF_PYTHON_ENV_PATH ; ./install.sh esp32s3
The
IDF_PYTHON_ENV_PATHvariable specifies where theesp-idfenvironment is installed. I'm including it in case you have another version already loaded in your environment.
Whenever you open a new terminal, you need to reload the ESP-IDF environment:
cd ~/src/picokeys/esp-idf
# Load the ESP-IDF environment:
. ./export.sh
# Check the version:
$ idf.py --version
ESP-IDF v5.5.1
Downloading and Compiling Pico FIDO
With the ESP-IDF environment loaded, let's clone and compile Pico FIDO:
cd ~/src/picokeys
git clone --recursive https://github.com/polhenarejos/pico-fido.git && cd pico-fido
Failure to Enforce the alwaysUv Policy
The alwaysUv setting specifies that the authenticator (physical security key) must always require user verification (via PIN or biometrics) before performing authentication operations. However, I found a possible inconsistency in src/fido/cbor_get_assertion.c: the firmware checks authorization when it is provided, but doesn't explicitly require that authorization on every execution path when alwaysUv is enabled.
This could allow certain authentication operations to be performed without user verification, bypassing the configured security policy. To fix this, edit the following file:
vim src/fido/cbor_get_assertion.c
Replace this:
if (next == false) {
if (pinUvAuthParam.present == true) {
if (pinUvAuthParam.len == 0 || pinUvAuthParam.data == NULL) {
if (check_user_presence() == false) {
CBOR_ERROR(CTAP2_ERR_OPERATION_DENIED);
}
With this:
if (next == false) {
bool alwaysUv = (get_opts() & FIDO2_OPT_AUV) ||
(file_has_data(ef_pin) && !keydev_unlocked);
if (alwaysUv && pinUvAuthParam.present == false) {
CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED);
}
if (pinUvAuthParam.present == true) {
if (pinUvAuthParam.len == 0 || pinUvAuthParam.data == NULL) {
if (check_user_presence() == false) {
CBOR_ERROR(CTAP2_ERR_OPERATION_DENIED);
}
Memory Validation During U2F Processing
I found a possible problem with how Pico FIDO validates the length of data received during U2F operations. Although this is an older protocol, many systems may still use U2F, so it's worth fixing. The firmware uses a length supplied by the client (such as a browser or any other system requesting the operation) without fully checking whether it matches the actual amount of data received.
This could cause out-of-bounds memory reads, crashes, or unexpected behavior when the device receives malformed messages. Someone with access to the computer connected to Pico FIDO could send crafted USB commands to try to exploit this flaw. To address the problem, edit the following file:
vim src/fido/cmd_authenticate.c
Replace these lines:
if (apdu.nc < CTAP_CHAL_SIZE + CTAP_APPID_SIZE + 1 + 1) {
return SW_WRONG_DATA();
}
if (req->keyHandleLen < KEY_HANDLE_LEN) {
return SW_INCORRECT_PARAMS();
}
With these:
const size_t key_handle_offset = offsetof(CTAP_AUTHENTICATE_REQ, keyHandle);
if (apdu.nc < key_handle_offset) {
return SW_WRONG_DATA();
}
if (req->keyHandleLen < KEY_HANDLE_LEN ||
req->keyHandleLen > CTAP_MAX_KH_SIZE) {
return SW_INCORRECT_PARAMS();
}
if ((size_t)apdu.nc < key_handle_offset + req->keyHandleLen) {
return SW_WRONG_DATA();
}
In the same file, replace the two lines below:
uint8_t *tmp_kh = (uint8_t *) calloc(1, req->keyHandleLen);
memcpy(tmp_kh, req->keyHandle, req->keyHandleLen);
With these:
uint8_t *tmp_kh = (uint8_t *) calloc(1, req->keyHandleLen);
if (tmp_kh == NULL) {
mbedtls_ecp_keypair_free(&key);
return SW_MEMORY_FAILURE();
}
memcpy(tmp_kh, req->keyHandle, req->keyHandleLen);
Compiling FIDO
Before compiling, I'd like to mention two security issues I found in the FIDO firmware. Although I've already implemented the fixes and validated the results in my own tests, I also submitted a suggestion to the official project so the developers can review both the fixes and their impact, and potentially include these improvements in the official code in the future.
The test scripts were generated using AI and reviewed by me. You can access the scripts on my GitLab.
Now let's compile.
# If you compiled HSM before FIDO, run:
idf.py fullclean
# Set ESP32-S3 as the target microcontroller for firmware compilation:
idf.py set-target esp32s3
# Compile the source code using the YubiKey5 VID/PID:
idf.py -DVIDPID=Yubikey5 build
# To compile using the PicoKeys VID/PID, run the command below:
idf.py build
Compiling Pico FIDO generates several .bin files, including the bootloader, partition table, and main firmware. The build/flash_args file tells us which files to flash and at which flash addresses. The command below combines these files into a single image, which I'm calling pico_fido_esp32-s3.bin. This step is optional if you plan to flash the device using idf.py flash.
However, this command is necessary if you want to create a single .bin file to distribute or flash using another method, such as esptool.py.
mkdir -p release && cd build
# Merge the '.bin' files generated during compilation:
esptool.py --chip ESP32-S3 merge_bin -o ../release/pico_fido_esp32-s3.bin @flash_args && cd ..
Downloading and Compiling Pico HSM
The tutorial described here only works with the Pico HSM main branch. I'll keep these build instructions here because a lot has changed since version 6.6, including the way the firmware is compiled, so I believe this pattern will continue with minor adjustments. But keep in mind that using the main branch will cause the HSM not to be recognized (at least at the time I wrote this tutorial).
Pico HSM also uses ESP-IDF targeting ESP32-S3, and we'll use version 5.5.1 here as well, for the same reasons as with FIDO.
cd ~/src/picokeys
git clone --recursive https://github.com/polhenarejos/pico-hsm.git && cd pico-hsm
In the code I tested, two adjustments were required before compilation. These changes make ESP-IDF register the HSM component and find the libcvc dependency. Edit the CMakeLists.txt file in the Pico HSM root directory:
vim CMakeLists.txt
Inside if(ESP_PLATFORM) (the first block, usually around line 23), define PICOKEYS_COMPONENTS as hsm before the first ESP-IDF include(...). The original section looks like this:
if(ESP_PLATFORM)
set(EXTRA_COMPONENT_DIRS pico-keys-sdk/config/esp32/components src/hsm)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
else()
It should look like this:
if(ESP_PLATFORM)
set(PICOKEYS_COMPONENTS hsm)
set(EXTRA_COMPONENT_DIRS pico-keys-sdk/config/esp32/components src/hsm)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
else()
Now let's make ESP-IDF find the libcvc dependency. Start by editing the Pico Keys SDK importer:
vim pico-keys-sdk/picokeys_sdk_import.cmake
Inside if(ESP_PLATFORM), there is a set(EXTRA_COMPONENT_DIRS ...) definition. Find it (usually around line 73 in the file on the main branch). The full original section looks like this:
if(ESP_PLATFORM)
if(DEFINED CONFIG_TINYUSB_DESC_CUSTOM_VID)
set(USB_VID CONFIG_TINYUSB_DESC_CUSTOM_VID)
endif()
if(DEFINED CONFIG_TINYUSB_DESC_CUSTOM_PID)
set(USB_PID CONFIG_TINYUSB_DESC_CUSTOM_PID)
endif()
if(NOT DEFINED ENABLE_POWER_ON_RESET)
set(ENABLE_POWER_ON_RESET 0)
endif()
if(NOT DEFINED ENABLE_PQC)
set(ENABLE_PQC 0)
endif()
if(NOT DEFINED SDKCONFIG_DEFAULTS AND NOT DEFINED ENV{SDKCONFIG_DEFAULTS})
set(SDKCONFIG_DEFAULTS "pico-keys-sdk/config/esp32/sdkconfig.defaults")
endif()
set(EXTRA_COMPONENT_DIRS
src/${PICOKEYS_COMPONENTS}
pico-keys-sdk/config/esp32/components/mbedtls
pico-keys-sdk/config/esp32/components/pico-keys-sdk
)
set(COMPONENTS ${PICOKEYS_COMPONENTS} pico-keys-sdk)
if (USB_ITF_HID)
list(APPEND EXTRA_COMPONENT_DIRS
pico-keys-sdk/config/esp32/components/tinycbor
)
list(APPEND COMPONENTS tinycbor)
endif()
if(ENABLE_PQC)
list(APPEND EXTRA_COMPONENT_DIRS
pico-keys-sdk/config/esp32/components/mlkem512
pico-keys-sdk/config/esp32/components/mlkem768
pico-keys-sdk/config/esp32/components/mlkem1024
)
endif()
endif()
After set(EXTRA_COMPONENT_DIRS ...), add:
if(ENABLE_LIBCVC)
list(APPEND EXTRA_COMPONENT_DIRS
pico-keys-sdk/config/esp32/components/libcvc
)
endif()
The new full section looks like this:
if(ESP_PLATFORM)
if(DEFINED CONFIG_TINYUSB_DESC_CUSTOM_VID)
set(USB_VID CONFIG_TINYUSB_DESC_CUSTOM_VID)
endif()
if(DEFINED CONFIG_TINYUSB_DESC_CUSTOM_PID)
set(USB_PID CONFIG_TINYUSB_DESC_CUSTOM_PID)
endif()
if(NOT DEFINED ENABLE_POWER_ON_RESET)
set(ENABLE_POWER_ON_RESET 0)
endif()
if(NOT DEFINED ENABLE_PQC)
set(ENABLE_PQC 0)
endif()
if(NOT DEFINED SDKCONFIG_DEFAULTS AND NOT DEFINED ENV{SDKCONFIG_DEFAULTS})
set(SDKCONFIG_DEFAULTS "pico-keys-sdk/config/esp32/sdkconfig.defaults")
endif()
set(EXTRA_COMPONENT_DIRS
src/${PICOKEYS_COMPONENTS}
pico-keys-sdk/config/esp32/components/mbedtls
pico-keys-sdk/config/esp32/components/pico-keys-sdk
)
if(ENABLE_LIBCVC)
list(APPEND EXTRA_COMPONENT_DIRS
pico-keys-sdk/config/esp32/components/libcvc
)
endif()
set(COMPONENTS ${PICOKEYS_COMPONENTS} pico-keys-sdk)
if (USB_ITF_HID)
list(APPEND EXTRA_COMPONENT_DIRS
pico-keys-sdk/config/esp32/components/tinycbor
)
list(APPEND COMPONENTS tinycbor)
endif()
if(ENABLE_PQC)
list(APPEND EXTRA_COMPONENT_DIRS
pico-keys-sdk/config/esp32/components/mlkem512
pico-keys-sdk/config/esp32/components/mlkem768
pico-keys-sdk/config/esp32/components/mlkem1024
)
endif()
endif()
This adjustment is necessary because the HSM component declares libcvc in REQUIRES, but the SDK importer does not include (at the time of writing) that component's directory in the list searched by ESP-IDF.
Now let's disable CONFIG_ESP_COREDUMP_ENABLE_TO_UART. With this option enabled, someone could trigger a failure on the ESP32-S3, causing a memory dump to be sent over UART, a serial communication interface used to send and receive data. The problem is that this dump could contain sensitive information such as cryptographic keys, PINs, and other data stored in RAM.
This feature also applies to FIDO, but it isn't present in the main branch, which is why I didn't document how to disable it there.
This feature could be exploited in an Information Disclosure attack, allowing someone with UART access to obtain sensitive data from the device.
vim sdkconfig.defaults
Change the line by commenting it out. This is how it looks before disabling it:
# Before disabling:
CONFIG_ESP_COREDUMP_ENABLE_TO_UART=y
This is how it should look after disabling it:
# CONFIG_ESP_COREDUMP_ENABLE_TO_UART is not set
Now let's compile Pico HSM:
cd ~/src/picokeys/esp-idf
# Load the ESP-IDF environment:
. ./export.sh
# Enter the Pico HSM project directory:
cd ~/src/picokeys/pico-hsm
# If you compiled FIDO before HSM, run:
idf.py fullclean
# Set ESP32-S3 as the target microcontroller for firmware compilation:
idf.py set-target esp32s3
# Compile the source code using the YubiHSM VID/PID:
idf.py -DVIDPID=YubiHSM build
# To compile using the PicoKeys VID/PID, run the command below:
idf.py build
As with FIDO, compiling HSM generates several .bin files. flash_args specifies which files to flash and at which addresses. merge_bin combines these files into one, but this is optional; we can flash the board with idf.py flash. However, this command is necessary if you want a single .bin file for distribution or for flashing through another method, such as esptool.py.
mkdir -p release && cd build
# Merge the '.bin' files generated during compilation:
esptool.py --chip ESP32-S3 merge_bin -o ../release/pico_hsm_esp32-s3.bin @flash_args && cd ..
Flashing the Firmware Without Secure Boot
Let's flash the firmware without Secure Boot. First, put the ESP32-S3 into Download Mode:
- Hold BOOT, press and release RESET, then release BOOT.
Next, run this command to see which port the board appeared on:
$ ls -l /dev/ttyACM* /dev/ttyUSB*
/dev/ttyACM0
In my case it was /dev/ttyACM0. I'll use this name in the command below to flash the firmware. If yours is different, use the port shown on your system, but make sure it belongs to the correct ESP32-S3 board you want to flash.
# For FIDO:
cd ~/src/picokeys/pico-fido && esptool.py --chip ESP32-S3 \
-p /dev/ttyACM0 \
write_flash 0x0 \
release/pico_fido_esp32-s3.bin
# For HSM:
cd ~/src/picokeys/pico-hsm && esptool.py --chip ESP32-S3 \
-p /dev/ttyACM0 \
write_flash 0x0 \
release/pico_hsm_esp32-s3.bin
Compiling the Firmware with Secure Boot
The ESP32-S3 includes security features that help protect the device against unauthorized firmware changes. Secure Boot verifies the firmware's digital signature before executing it, allowing only images signed with an authorized key to boot. This prevents someone from replacing the firmware stored in flash memory, since the microcontroller will refuse to execute a new image if its signature is invalid.
Simply enabling Secure Boot v2 on the ESP32-S3 automatically applies some security protections, even without using Secure Lock. In my case, the two additional Secure Boot digest slots were revoked, the JTAG interface was permanently disabled, and the device switched to Secure Download Mode.
Revoking the additional digest slots prevents someone from registering new signing keys and then executing firmware that the board should not originally accept. This closes off one possible way to bypass Secure Boot protection.
With the JTAG interface permanently disabled, someone can no longer use that interface to debug the processor, inspect memory, or interfere with firmware execution. This makes attacks requiring physical access more difficult.
Secure Download Mode restricts the commands available in download mode, preventing operations such as directly reading eFuses and reducing opportunities to manipulate the device through that interface. This makes it harder to use download mode to circumvent configured protections.
All these protections were enabled automatically when I enabled Secure Boot. However, this behavior may change in the future, which is why I included the commits used at the beginning of this page.
Do I Need to Enable Flash Encryption?
In the code I analyzed, both Pico FIDO and Pico HSM use containers with authenticated encryption to protect sensitive objects such as private keys and credentials. This protects the data at the firmware level, but it does not mean the entire flash memory is encrypted.
The ESP32-S3's own Flash Encryption feature adds another layer: it encrypts the partitions configured for it, making it harder to read the firmware and other data by directly accessing the flash. This protection complements the container encryption, but does not prevent data access while firmware is running on the device.
In theory, the sensitive data stored by Pico FIDO and Pico HSM in these containers is encrypted before being written to flash. Therefore, directly reading the flash should not reveal that data without the key. Since the data is encrypted and the keys used to decrypt it are stored in a protected form in flash, a flash dump alone should not reveal the keys in plaintext.
Flash Encryption provides additional protection because it also encrypts the configured partitions. This makes analyzing flash contents more difficult for someone with direct access to the memory.
Generating the Signing Keys
To use Secure Boot, we need to generate a cryptographic key pair:
- Private key: used to sign the firmware during compilation or afterward. It must be stored somewhere secure, outside the ESP32-S3.
- Public key: used to verify that the firmware signature is legitimate. The ESP32-S3 stores a cryptographic summary (digest) of this key in its eFuses.
The process is relatively simple: when the ESP32-S3 powers on, Secure Boot checks whether the firmware has a valid signature produced by a private key corresponding to one of the public keys authorized on the device. If verification fails, the firmware won't run.
Secure Boot V2, used by the ESP32-S3, works with RSA-3072 signatures. First, let's create a directory to store the keys:
# Create the directory
mkdir -p ~/.config/picokeys/signing
# Allow access only to the owner
chmod 700 ~/.config/picokeys/signing
# Restrict permissions on new files
umask 077
Now let's use espsecure.py, a tool provided by ESP-IDF, to generate an RSA-3072 private key:
espsecure.py generate_signing_key --version 2 --scheme rsa3072 ~/.config/picokeys/signing/secure-boot.pem
The command above creates the secure-boot.pem file containing the private key used to sign the firmware. The parameters are:
-
generate_signing_key: generates a new cryptographic key. -
--version 2: specifies Secure Boot V2. -
--scheme rsa3072: selects the RSA algorithm with a 3072-bit key.
If you plan to use Pico HSM on another board, we can generate a second key:
espsecure.py generate_signing_key --version 2 --scheme rsa3072 ~/.config/picokeys/signing/hsm-secure-boot.pem
Finally, let's make sure only the owner can read or modify the files:
chmod 600 ~/.config/picokeys/signing/*-secure-boot.pem
Associating the Key Digest with PicoKeys Firmware
Now that we have our private key, we need to make a small change to the PicoKeys source code to use our own signing key. This is necessary because the PicoKeys SDK has a hard-coded public key digest, meaning a digest defined directly in the source code. This simplifies enabling Secure Boot and Secure Lock through the PicoKey App, since the firmware has a known digest for provisioning and users don't have to generate and configure their own keys.
However, because we're compiling the firmware with our own private key, we need to replace that digest with the one corresponding to our public key. That way, we ensure Secure Boot uses the same signing key configured during firmware compilation.
The digest is a 32-byte cryptographic summary calculated from the public key. Think of it as a fingerprint of the public key: it lets Secure Boot identify which keys are authorized to sign firmware running on the microcontroller. The ESP32-S3 stores this digest in the eFuses.
Generating the Public Key
Let's use espsecure.py again, but this time to calculate the public key digest corresponding to the private key we generated earlier:
espsecure.py digest_sbv2_public_key \
--keyfile ~/.config/picokeys/signing/secure-boot.pem \
--output public-digest.bin
The parameters are:
-
digest_sbv2_public_key: calculates the public key digest for Secure Boot V2. -
--keyfile: specifies the file containing our private key. The tool extracts the public key information to calculate the digest. -
--output: specifies the name of the file where the digest will be stored.
The command generates public-digest.bin, containing the 32 bytes we need to add to the PicoKeys source code.
Converting the Digest to the Format Used by PicoKeys
The generated file is binary, but the PicoKeys SDK stores the digest as a byte array in C source code. To make things easier, we can use a small Python script to convert those bytes into hexadecimal format:
python3 - public-digest.bin <<'PY'
from pathlib import Path
import sys
digest = Path(sys.argv[1]).read_bytes()
if len(digest) != 32:
raise SystemExit(f"Esperados 32 bytes; recebidos {len(digest)}")
for start in range(0, 32, 8):
print(" " + ", ".join(f"0x{b:02x}" for b in digest[start:start+8]) + ",")
PY
The script reads the file, checks that it contains exactly 32 bytes, and prints the hexadecimal output in the format needed for the source code. The output looks like this:
0x0c, 0x1e, 0xce, 0xf3, 0xb4, 0x8f, 0x4a, 0x81,
0x45, 0x6c, 0x85, 0x39, 0x15, 0xcc, 0x05, 0x36,
0xbe, 0x23, 0x24, 0xee, 0xac, 0x8e, 0x3b, 0xb5,
0x77, 0x6f, 0x2d, 0xb9, 0x62, 0x38, 0x75, 0x6a
Adding the Digest to PicoKeys FIDO and HSM Source Code
For both FIDO and HSM, edit the file below:
vim pico-keys-sdk/src/otp/otp_esp32.c
In the correct file, find the following variable:
static const uint8_t esp_secure_boot_digest[32]
It contains the 32 bytes of the digest originally used by PicoKeys. Now replace the existing values with the bytes we just generated, keeping the array structure:
static const uint8_t esp_secure_boot_digest[32] = {
0x0c, 0x1e, 0xce, 0xf3, 0xb4, 0x8f, 0x4a, 0x81,
0x45, 0x6c, 0x85, 0x39, 0x15, 0xcc, 0x05, 0x36,
0xbe, 0x23, 0x24, 0xee, 0xac, 0x8e, 0x3b, 0xb5,
0x77, 0x6f, 0x2d, 0xb9, 0x62, 0x38, 0x75, 0x6a
};
If you have separate Pico FIDO and Pico HSM directories, you'll need to repeat this procedure in each one. Use the same digest if it's the same board, or a different one if you're using another board.
Configuring and Signing Secure Boot for FIDO and HSM
I recommend performing this procedure in a separate directory to avoid confusing firmware configured with your signing key with another version that doesn't have that configuration. Since we're dealing with Secure Boot and ESP32-S3 eFuses, a mistake could result in permanently programming an incorrect digest, preventing new firmware from booting.
In the root directory of each project, run the command below:
# Clean the environment and start again:
idf.py fullclean
idf.py set-target esp32s3
# Now edit the important settings:
idf.py menuconfig
Go to Security features and check Enable hardware Secure Boot in bootloader. A new menu will appear. There, select Select secure boot version (Enable Secure Boot version 2), and make sure you see (X) Enable Secure Boot version 2 (in my case everything was already correct, but I wanted to make this clear).
Still under Security features, select Secure boot private signing key (NEW). In my case, it was set to secure_boot_signing_key.pem. Replace that value with the path and filename of the private key we generated earlier. In my case, I configured it exactly as follows: /home/bruno/.config/picokeys/signing/secure-boot.pem.
Go back to the main screen by pressing backspace, select Partition Table, then Offset of partition table. Change the value from 0x8000 to 0x10000.
Press s to save and q to quit. To make sure everything is correct, we can check:
$ grep -iE 'CONFIG_SECURE_BOOT=y|CONFIG_PARTITION_TABLE_OFFSET|CONFIG_SECURE_BOOT_V2_ENABLED=y|CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES=y|CONFIG_SECURE_BOOT_SIGNING_KEY=' /home/bruno/src/picokeys/pico-fido/sdkconfig
CONFIG_SECURE_BOOT=y
CONFIG_SECURE_BOOT_V2_ENABLED=y
CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES=y
CONFIG_SECURE_BOOT_SIGNING_KEY="/home/bruno/.config/picokeys/signing/secure-boot.pem"
CONFIG_PARTITION_TABLE_OFFSET=0x10000
Generating Signed FIDO and HSM Firmware
Now that we've laid the groundwork, let's build new firmware, but this time signed.
For FIDO:
cd ~/src/picokeys/pico-fido
# Compile the source code using the YubiKey5 VID/PID:
idf.py -DVIDPID=Yubikey5 build
For HSM:
cd ~/src/picokeys/pico-hsm
# Compile the source code using the YubiKey5 VID/PID:
idf.py -DVIDPID=YubiHSM build
Now, in the directory where we cloned the firmware (either FIDO or HSM), let's validate the bootloader and firmware:
#### FIDO
# Validate the bootloader signature:
$ idf.py secure-verify-signature --keyfile /home/bruno/.config/picokeys/signing/secure-boot.pem bootloader/bootloader.bin
Executing action: secure-verify-signature
Running espsecure in directory /home/bruno/src/picokeys/pico-fido/build
Executing "/home/bruno/.espressif/python_env/idf5.5_py3.14_env/bin/python -m espsecure verify_signature --version 2 --keyfile /home/bruno/.config/picokeys/signing/secure-boot.pem bootloader/bootloader.bin"...
espsecure.py v4.12.0
Signature block 0 is valid (RSA).
Signature block 0 verification successful using the supplied key (RSA).
Signature block 1 invalid. Skipping.
Signature block 2 invalid. Skipping.
# Validate the firmware signature:
$ idf.py secure-verify-signature --keyfile /home/bruno/.config/picokeys/signing/secure-boot.pem pico_fido.bin
Executing action: secure-verify-signature
Running espsecure in directory /home/bruno/src/picokeys/pico-fido/build
Executing "/home/bruno/.espressif/python_env/idf5.5_py3.14_env/bin/python -m espsecure verify_signature --version 2 --keyfile /home/bruno/.config/picokeys/signing/secure-boot.pem pico_fido.bin"...
espsecure.py v4.12.0
Signature block 0 is valid (RSA).
Signature block 0 verification successful using the supplied key (RSA).
Signature block 1 invalid. Skipping.
Signature block 2 invalid. Skipping.
#### HSM
# Validate the bootloader signature:
$ idf.py secure-verify-signature --keyfile /home/bruno/.config/picokeys/signing/secure-boot.pem bootloader/bootloader.bin
Executing action: secure-verify-signature
Running espsecure in directory /home/bruno/src/picokeys/pico-hsm/build
Executing "/home/bruno/.espressif/python_env/idf5.5_py3.12_env/bin/python -m espsecure verify_signature --version 2 --keyfile /home/bruno/.config/picokeys/signing/secure-boot.pem bootloader/bootloader.bin"...
espsecure.py v4.12.0
Signature block 0 is valid (RSA).
Signature block 0 verification successful using the supplied key (RSA).
Signature block 1 invalid. Skipping.
Signature block 2 invalid. Skipping.
# Validate the firmware signature:
$ idf.py secure-verify-signature --keyfile /home/bruno/.config/picokeys/signing/secure-boot.pem pico_hsm.bin
Executing action: secure-verify-signature
Running espsecure in directory /home/bruno/src/picokeys/pico-hsm/build
Executing "/home/bruno/.espressif/python_env/idf5.5_py3.12_env/bin/python -m espsecure verify_signature --version 2 --keyfile /home/bruno/.config/picokeys/signing/secure-boot.pem pico_hsm.bin"...
espsecure.py v4.12.0
Signature block 0 is valid (RSA).
Signature block 0 verification successful using the supplied key (RSA).
Signature block 1 invalid. Skipping.
Signature block 2 invalid. Skipping.
Flashing the Secure Boot Image for FIDO and HSM
Let's generate the merged file to flash afterward:
mkdir -p release && cd build
# Merge the '.bin' files generated during FIDO compilation:
esptool.py --chip ESP32-S3 merge_bin -o ../release/pico_fido_esp32-s3.bin \
--flash_mode dio --flash_freq 80m --flash_size keep \
0x0 bootloader/bootloader.bin \
0x10000 partition_table/partition-table.bin \
0x20000 pico_fido.bin
# Merge the '.bin' files generated during HSM compilation:
esptool.py --chip ESP32-S3 merge_bin -o ../release/pico_hsm_esp32-s3.bin \
--flash_mode dio --flash_freq 80m --flash_size keep \
0x0 bootloader/bootloader.bin \
0x10000 partition_table/partition-table.bin \
0x20000 pico_hsm.bin
Compilation generates separate files for the bootloader, partition table, and firmware. The merge_bin command above combines them into a single image. However, we need to specify the flash address where each file will be written, since .bin files don't contain that position themselves. The partition table offset was increased from 0x8000 to 0x10000 because the build showed that the bootloader was 0x9060 bytes, larger than the original 0x8000 space reserved for it.
Once the image is ready, we can flash it onto the board:
# For FIDO:
esptool.py --chip ESP32-S3 \
-p /dev/ttyACM0 \
write_flash 0x0 \
release/pico_fido_esp32-s3.bin
# For HSM:
esptool.py --chip ESP32-S3 \
-p /dev/ttyACM0 \
write_flash 0x0 \
release/pico_hsm_esp32-s3.bin
Disconnect and reconnect the board, enter Download Mode, then run the command below:
$ esptool.py --chip ESP32-S3 --port /dev/ttyACM0 --no-stub get_security_info
esptool.py v4.12.0
Serial port /dev/ttyACM0
Connecting...
Chip is ESP32-S3 in Secure Download Mode
Enabling default SPI flash mode...
Security Information:
=====================
Flags: 0x000000f5 (0b11110101)
Key Purposes: (9, 0, 0, 0, 0, 0, 12)
BLOCK_KEY0 - SECURE_BOOT_DIGEST0
BLOCK_KEY1 - USER/EMPTY
BLOCK_KEY2 - USER/EMPTY
BLOCK_KEY3 - USER/EMPTY
BLOCK_KEY4 - USER/EMPTY
BLOCK_KEY5 - USER/EMPTY
Chip ID: 9
API Version: 0
Secure Boot: Enabled
Secure Boot Key Revocation Status:
Secure Boot Key1 is Revoked
Secure Boot Key2 is Revoked
Flash Encryption: Disabled
SPI Boot Crypt Count (SPI_BOOT_CRYPT_CNT): 0x0
JTAG: Permanently Disabled
Hard resetting via RTS pin...
The BLOCK_KEY0 - SECURE_BOOT_DIGEST0 line shows that the digest of the public key used by Secure Boot was programmed into eFuse block BLOCK_KEY0. We can also see Secure Boot Key1 is Revoked and Secure Boot Key2 is Revoked, indicating that slots 1 and 2 have been revoked and can no longer be used to validate signatures. Therefore, only the key associated with SECURE_BOOT_DIGEST0 remains authorized.
↑ Back to the Table of Contents
Part 3 — Secure Boot and Flash Encryption
Compiling the Firmware with Secure Boot and Flash Encryption
I'm not going to spend time explaining Flash Encryption again because I've already covered it. If you want to understand it, read the earlier section "Do I Need to Enable Flash Encryption?".
In this procedure, the idea is to generate the Flash Encryption key on the computer, keep a secure backup of it, and encrypt the binaries before flashing them using ROM Download Mode. For this to work, the same key must also be stored and protected in an ESP32-S3 eFuse block. The hardware uses this key to decrypt the contents during execution.
As in Part 2, I'll use ESP-IDF 5.5.1, which is used by the PicoKeys project. The projects were compiled using the following commits:
### FIDO
Hash: ade770281dd4fd8c627add34ed078f56ec40a07b
tag: nightly-main and nightly-development
### HSM
Hash: 5d6a32c81b2367fb83e5320400f61217d2bd0d2f
tag: nightly-master and nightly-development
Cloning the Projects and Installing ESP-IDF 5.5.1
On Ubuntu, install the dependencies specified by ESP-IDF. Then clone the repositories:
# Create a new directory for this project:
mkdir -p ~/src/picokeys-fe && cd ~/src/picokeys-fe
# Clone ESP-IDF version 5.5.1:
git clone --branch v5.5.1 --recursive https://github.com/espressif/esp-idf.git esp-idf
# Clone Pico FIDO:
git clone --recursive https://github.com/polhenarejos/pico-fido.git
# Clone Pico HSM:
git clone --recursive https://github.com/polhenarejos/pico-hsm.git
Now install ESP-IDF and load the environment:
# Enter the ESP-IDF project directory:
cd esp-idf
# Install ESP-IDF for ESP32-S3:
unset IDF_PYTHON_ENV_PATH ; ./install.sh esp32s3
# Load the ESP-IDF environment:
. ./export.sh
# Check the version:
$ idf.py --version
ESP-IDF v5.5.1
# Return to the base directory:
cd ~/src/picokeys-fe
Required Adjustments for HSM and FIDO
As described in the other procedures, we need to make the necessary adjustments to both FIDO and HSM to fix the issues related to key security, along with any other required changes.
I won't show the steps again here to avoid repeating the procedure already covered in Part 2.
Generating the Keys
To use Secure Boot V2, we need an RSA-3072 private key. It will be used to sign the bootloader and application. The ESP32-S3 stores the digest of the corresponding public key in its eFuses.
The Flash Encryption key is a separate key. It uses AES-XTS and will be used to encrypt the binaries on the computer. This key must also be provisioned on the ESP32-S3 in an eFuse block with the purpose XTS_AES_128_KEY.
Let's start by creating the directories and restricting permissions:
mkdir -p ~/.config/picokeys/signing
mkdir -p ~/.config/picokeys/flash-encryption
chmod 700 ~/.config/picokeys/signing ~/.config/picokeys/flash-encryption
umask 077
Now generate the signing key (Secure Boot):
espsecure.py generate_signing_key --version 2 --scheme rsa3072 \
~/.config/picokeys/signing/secure-boot.pem
chmod 600 ~/.config/picokeys/signing/secure-boot.pem
Let's get the board's MAC address. Here, the MAC will only serve as a name—an identifier that could be anything. I'll use it to make it easier to identify which board the key belongs to. Enable Download Mode (hold BOOT, press and release RESET, then release BOOT).
Next, run the command below to see which port the board appeared on:
$ ls -l /dev/ttyACM* /dev/ttyUSB*
/dev/ttyACM0
In my case it was /dev/ttyACM0. I'll use this name in the command below to flash the firmware. If yours is different, use the port shown on your system, but make sure it belongs to the correct ESP32-S3 board you want to flash. Now let's check the board's MAC address:
$ esptool.py --chip esp32s3 --port /dev/ttyACM0 read_mac
esptool.py v4.12.0
Serial port /dev/ttyACM0
Connecting...
Chip is ESP32-S3 (QFN56) (revision v0.2)
Features: WiFi, BLE, Embedded PSRAM 8MB (AP_3v3)
Crystal is 40MHz
USB mode: USB-Serial/JTAG
MAC: 68:ee:8f:51:e0:38
Uploading stub...
Running stub...
Stub running...
MAC: 68:ee:8f:51:e0:38
Hard resetting via RTS pin...
Now that we have the MAC, we'll use it as the ID (68ee8f51e038). The command below generates a 256-bit key (used for Flash Encryption):
espsecure.py generate_flash_encryption_key \
~/.config/picokeys/flash-encryption/pico-68ee8f51e038.bin
chmod 600 ~/.config/picokeys/flash-encryption/pico-68ee8f51e038.bin
Replace
68ee8f51e038with your own identifier. Use a different Flash Encryption key for each device and keep a protected backup. If you lose this key, you will no longer be able to use this board.
To store this file somewhere secure, such as a password manager, we can do the following:
xxd -p -c 256 ~/.config/picokeys/flash-encryption/pico-68ee8f51e038.bin
You will see a 256-bit hash:
11c860d531eec46bb9b29319a798da103e7c121c7dcbe9aa2b251a5a855daae6
Save this value somewhere secure. To restore it to a binary file, do the following:
xxd -r -p chave.hex pico-68ee8f51e038-restaurado.bin
The chave.hex file must contain the 256-bit hash.
Associating the Key Digest with PicoKeys Firmware
Now let's calculate the public key digest to validate the firmware signature:
espsecure.py digest_sbv2_public_key \
--keyfile ~/.config/picokeys/signing/secure-boot.pem \
--output ~/.config/picokeys/signing/public-digest.bin
The command above generates a 32-byte binary file. Next, we need to convert its contents to hexadecimal format:
python3 - ~/.config/picokeys/signing/public-digest.bin <<'PY'
from pathlib import Path
import sys
digest = Path(sys.argv[1]).read_bytes()
if len(digest) != 32:
raise SystemExit(f"Esperados 32 bytes; recebidos {len(digest)}")
for start in range(0, 32, 8):
print(" " + ", ".join(f"0x{b:02x}" for b in digest[start:start+8]) + ",")
PY
For both FIDO and HSM, edit the file:
vim pico-keys-sdk/src/otp/otp_esp32.c
Find esp_secure_boot_digest[32] and replace the 32 bytes with the output of the previous command, as demonstrated in the earlier tutorial. This tells the SDK which digest to use when validating the firmware signature. It does not sign the firmware or write anything to the eFuses.
Configuring Secure Boot and Flash Encryption
In the root directory of each project, clean the build and configure the ESP32-S3 target:
idf.py fullclean
idf.py set-target esp32s3
idf.py menuconfig
Under Security features, configure:
-
Enable hardware Secure Boot in bootloader; -
Select secure boot version (Enable Secure Boot version 2); -
Sign binaries during build; -
Secure boot private signing key: path to the RSA private key corresponding to the digest accepted by the board; -
Enable flash encryption on boot; -
Enable usage mode:Release; -
UART ROM download mode:(Permanently switch to Secure mode (recommended)).
Under Partition Table, change the partition table offset from 0x8000 to 0x10000. Save with s and quit with q. Then verify that the options are correct:
$ grep -E \
-e '^CONFIG_SECURE_BOOT=' \
-e '^CONFIG_SECURE_BOOT_V2_ENABLED=' \
-e '^CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES=' \
-e '^CONFIG_SECURE_BOOT_SIGNING_KEY=' \
-e '^CONFIG_SECURE_FLASH_ENC_ENABLED=' \
-e '^CONFIG_SECURE_FLASH_ENCRYPTION_MODE_RELEASE=' \
-e '^CONFIG_SECURE_FLASH_ENCRYPTION_AES128=' \
-e '^CONFIG_SECURE_ENABLE_SECURE_ROM_DL_MODE=' \
-e '^CONFIG_PARTITION_TABLE_OFFSET=' \
sdkconfig
CONFIG_SECURE_BOOT=y
CONFIG_SECURE_BOOT_V2_ENABLED=y
CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES=y
CONFIG_SECURE_BOOT_SIGNING_KEY="/home/bruno/.config/picokeys/signing/secure-boot.pem"
CONFIG_SECURE_FLASH_ENC_ENABLED=y
CONFIG_SECURE_FLASH_ENCRYPTION_AES128=y
CONFIG_SECURE_FLASH_ENCRYPTION_MODE_RELEASE=y
CONFIG_SECURE_ENABLE_SECURE_ROM_DL_MODE=y
CONFIG_PARTITION_TABLE_OFFSET=0x10000
Edit the following file, whether you're using FIDO or HSM:
vim pico-keys-sdk/config/esp32/partitions.csv
The default table already uses factory at 0x20000 and part0 at 0x200000, with a size of 1M. We don't need to change those values. The important part is to add the encrypted flag at the end of the part0 line, so it looks like this:
part0, 0x40, 0x1, 0x200000, 1M, encrypted
ESP-IDF encrypts firmware partitions. The part0 partition, however, is a data partition, so we need to add the encrypted flag to have it encrypted as well.
Generating and Validating the Signed FIDO and HSM Firmware
Let's load the ESP-IDF 5.5.1 environment again (just to be safe) and compile Pico FIDO:
. ~/src/picokeys-fe/esp-idf/export.sh && export IDF_TARGET=esp32s3
cd ~/src/picokeys-fe/pico-fido
idf.py -DIDF_TARGET=esp32s3 -DVIDPID=Yubikey5 build
For Pico HSM:
. ~/src/picokeys-fe/esp-idf/export.sh && export IDF_TARGET=esp32s3
cd ~/src/picokeys-fe/pico-hsm
idf.py -DIDF_TARGET=esp32s3 -DVIDPID=YubiHSM build
After building, we need to validate the bootloader and application signatures, as in the previous procedure. For FIDO:
cd ~/src/picokeys-fe/pico-fido
$ idf.py secure-verify-signature \
--version 2 \
--keyfile ~/.config/picokeys/signing/secure-boot.pem \
bootloader/bootloader.bin
Executing action: secure-verify-signature
Running espsecure in directory /home/bruno/src/picokeys-fe/pico-fido/build
Executing "/home/bruno/.espressif/python_env/idf5.5_py3.12_env/bin/python -m espsecure verify_signature --version 2 --keyfile /home/bruno/.config/picokeys/signing/secure-boot.pem bootloader/bootloader.bin"...
espsecure.py v4.12.0
Signature block 0 is valid (RSA).
Signature block 0 verification successful using the supplied key (RSA).
Signature block 1 invalid. Skipping.
Signature block 2 invalid. Skipping.
$ idf.py secure-verify-signature \
--version 2 \
--keyfile ~/.config/picokeys/signing/secure-boot.pem \
pico_fido.bin
Executing action: secure-verify-signature
Running espsecure in directory /home/bruno/src/picokeys-fe/pico-fido/build
Executing "/home/bruno/.espressif/python_env/idf5.5_py3.12_env/bin/python -m espsecure verify_signature --version 2 --keyfile /home/bruno/.config/picokeys/signing/secure-boot.pem pico_fido.bin"...
espsecure.py v4.12.0
Signature block 0 is valid (RSA).
Signature block 0 verification successful using the supplied key (RSA).
Signature block 1 invalid. Skipping.
Signature block 2 invalid. Skipping.
For HSM:
cd ~/src/picokeys-fe/pico-hsm
idf.py secure-verify-signature \
--version 2 \
--keyfile ~/.config/picokeys/signing/secure-boot.pem \
bootloader/bootloader.bin
Executing action: secure-verify-signature
Running espsecure in directory /home/bruno/src/picokeys-fe/pico-hsm/build
Executing "/home/bruno/.espressif/python_env/idf5.5_py3.12_env/bin/python -m espsecure verify_signature --version 2 --keyfile /home/bruno/.config/picokeys/signing/secure-boot.pem bootloader/bootloader.bin"...
espsecure.py v4.12.0
Signature block 0 is valid (RSA).
Signature block 0 verification successful using the supplied key (RSA).
Signature block 1 invalid. Skipping.
Signature block 2 invalid. Skipping.
idf.py secure-verify-signature \
--version 2 \
--keyfile ~/.config/picokeys/signing/secure-boot.pem \
pico_hsm.bin
Executing action: secure-verify-signature
Running espsecure in directory /home/bruno/src/picokeys-fe/pico-hsm/build
Executing "/home/bruno/.espressif/python_env/idf5.5_py3.12_env/bin/python -m espsecure verify_signature --version 2 --keyfile /home/bruno/.config/picokeys/signing/secure-boot.pem pico_hsm.bin"...
espsecure.py v4.12.0
Signature block 0 is valid (RSA).
Signature block 0 verification successful using the supplied key (RSA).
Signature block 1 invalid. Skipping.
Signature block 2 invalid. Skipping.
For each command, signature block 0 must be valid. Blocks 1 and 2 may show as invalid if they were not used. Do not flash firmware whose signature has not been validated.
Preparing the Board and FE Key — First Firmware Only
Connect only the board you intend to use (this board must not already have Secure Boot enabled). Then put the board into Download Mode (hold BOOT, press and release RESET, then release BOOT).
Next, run the command below to see which port it appeared on:
$ ls -l /dev/ttyACM* /dev/ttyUSB*
/dev/ttyACM0
In my case it was /dev/ttyACM0. I'll use this name in the command below to flash the firmware. If yours is different, use the port shown on your system, but make sure it belongs to the correct ESP32-S3 board you want to flash.
Now we need to confirm a few things. Start by running the commands below:
$ espefuse.py --chip esp32s3 --port "/dev/ttyACM0" summary | grep -E 'ENABLE_SECURITY_DOWNLOAD|DIS_DOWNLOAD_MODE'
DIS_DOWNLOAD_MODE (BLOCK0) Set this bit to disable download mode (boot_mode[3 = False R/W (0b0)
ENABLE_SECURITY_DOWNLOAD (BLOCK0) Set this bit to enable secure UART download mode = False R/W (0b0)
$ esptool.py --chip esp32s3 --port "/dev/ttyACM0" get_security_info | grep -iE 'Secure Boot|Flash Encryption|SPI_BOOT_CRYPT_CNT|BLOCK_KEY1'
BLOCK_KEY1 - USER/EMPTY
Secure Boot: Disabled
Flash Encryption: Disabled
SPI Boot Crypt Count (SPI_BOOT_CRYPT_CNT): 0x0
These commands confirm several details, including:
- Secure Boot is disabled.
- Flash Encryption is disabled and
SPI_BOOT_CRYPT_CNTis0. - Secure Download Mode is disabled.
- The block intended for Flash Encryption is empty and available.
In the procedure below, BLOCK_KEY1 is reserved for Flash Encryption and BLOCK_KEY0 for the Secure Boot digest. If the commands above do not confirm that BLOCK_KEY1 is free, stop. Do not substitute another block without reviewing the key allocation.
As required by the official documentation, we now need to erase the flash (I don't see why this would be necessary, but let's follow the instructions exactly) and confirm that the AES-XTS key is 32 bytes long:
$ esptool.py --chip esp32s3 --port "/dev/ttyACM0" erase_flash
esptool.py v4.12.0
Serial port /dev/ttyACM0
Connecting...
Chip is ESP32-S3 (QFN56) (revision v0.2)
Features: WiFi, BLE, Embedded PSRAM 8MB (AP_3v3)
Crystal is 40MHz
USB mode: USB-Serial/JTAG
MAC: 68:ee:8f:51:e0:38
Uploading stub...
Running stub...
Stub running...
Erasing flash (this may take a while)...
Chip erase completed successfully in 4.7 seconds.
Hard resetting via RTS pin...
Now we'll write the Flash Encryption key into eFuse block BLOCK_KEY1:
$ espefuse.py --chip esp32s3 \
--port "/dev/ttyACM0" \
burn_key BLOCK_KEY1 "/home/bruno/.config/picokeys/flash-encryption/pico-68ee8f51e038.bin" XTS_AES_128_KEY
espefuse.py v4.12.0
Connecting...
=== Run "burn_key" command ===
Sensitive data will be hidden (see --show-sensitive-info)
Burn keys to blocks:
- BLOCK_KEY1 -> [?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??]
Reversing byte order for AES-XTS hardware peripheral
'KEY_PURPOSE_1': 'USER' -> 'XTS_AES_128_KEY'.
Disabling write to 'KEY_PURPOSE_1'.
Disabling read to key block
Disabling write to key block
Check all blocks for burn...
idx, BLOCK_NAME, Conclusion
[00] BLOCK0 is not empty
(written ): 0x0000000000000100000000000000d1f50000000000000000
(to write): 0x000000000000000000000000400000000000000201000200
(coding scheme = NONE)
[05] BLOCK_KEY1 is empty, will burn the new value
.
This is an irreversible operation!
Type 'BURN' (all capitals) to continue.
BURN
BURN BLOCK5 - OK (write block == read block)
BURN BLOCK0 - OK (all write block bits are set)
Reading updated efuses...
Successful
By default,
burn_keyprotects the key against both reading and writing.
The procedure will ask you to type BURN to continue. Type it and press Enter.
Now let's check get_security_info again. BLOCK_KEY1 should have the XTS_AES_128_KEY purpose and be protected against reading and writing.
$ esptool.py --chip esp32s3 --port "/dev/ttyACM0" get_security_info | grep -iE 'Secure Boot|Flash Encryption|SPI_BOOT_CRYPT_CNT|BLOCK_KEY1'
BLOCK_KEY1 - XTS_AES_128_KEY
Secure Boot: Disabled
Flash Encryption: Disabled
SPI Boot Crypt Count (SPI_BOOT_CRYPT_CNT): 0x0
Flashing the First Firmware onto the Board
Let's merge the files into a single binary:
### FIDO:
cd ~/src/picokeys-fe/pico-fido && mkdir -p release && cd build
esptool.py --chip ESP32-S3 merge_bin \
-o ../release/pico_fido_esp32-s3.bin \
--flash_mode dio --flash_freq 80m --flash_size keep \
0x0 bootloader/bootloader.bin \
0x10000 partition_table/partition-table.bin \
0x20000 pico_fido.bin && cd ..
### HSM:
cd ~/src/picokeys-fe/pico-hsm && mkdir -p release && cd build
esptool.py --chip ESP32-S3 merge_bin \
-o ../release/pico_hsm_esp32-s3.bin \
--flash_mode dio --flash_freq 80m --flash_size keep \
0x0 bootloader/bootloader.bin \
0x10000 partition_table/partition-table.bin \
0x20000 pico_hsm.bin && cd ..
In the layout above, the addresses used are:
| File | Address |
|---|---|
| Bootloader | 0x0 |
| Partition table | 0x10000 |
Application/Firmware (factory) |
0x20000 |
Data (part0), if it contains anything |
0x200000 |
Now let's flash the board:
# FIDO:
esptool.py --chip ESP32-S3 -p "/dev/ttyACM0" write_flash \
--flash_mode dio --flash_freq 80m --flash_size keep \
0x0 release/pico_fido_esp32-s3.bin
# HSM:
esptool.py --chip ESP32-S3 -p "/dev/ttyACM0" write_flash \
--flash_mode dio --flash_freq 80m --flash_size keep \
0x0 release/pico_hsm_esp32-s3.bin
Disconnect and reconnect the board, enter Download Mode, then run the command below:
$ esptool.py --chip ESP32-S3 --port /dev/ttyACM0 --no-stub get_security_info
esptool.py v4.12.0
Serial port /dev/ttyACM0
Connecting...
Chip is ESP32-S3 in Secure Download Mode
Enabling default SPI flash mode...
Security Information:
=====================
Flags: 0x000006f5 (0b11011110101)
Key Purposes: (9, 4, 0, 0, 0, 0, 12)
BLOCK_KEY0 - SECURE_BOOT_DIGEST0
BLOCK_KEY1 - XTS_AES_128_KEY
BLOCK_KEY2 - USER/EMPTY
BLOCK_KEY3 - USER/EMPTY
BLOCK_KEY4 - USER/EMPTY
BLOCK_KEY5 - USER/EMPTY
Chip ID: 9
API Version: 0
Secure Boot: Enabled
Secure Boot Key Revocation Status:
Secure Boot Key1 is Revoked
Secure Boot Key2 is Revoked
Flash Encryption: Enabled
SPI Boot Crypt Count (SPI_BOOT_CRYPT_CNT): 0x7
Dcache in UART download mode: Disabled
Icache in UART download mode: Disabled
JTAG: Permanently Disabled
Hard resetting via RTS pin...
The BLOCK_KEY0 - SECURE_BOOT_DIGEST0 line shows that the Secure Boot public key digest was programmed into BLOCK_KEY0. The messages Secure Boot Key1 is Revoked and Secure Boot Key2 is Revoked indicate that slots 1 and 2 have been revoked. Therefore, slot 0 remains authorized to validate signatures.
We can also see that Flash Encryption is active. The SPI_BOOT_CRYPT_CNT: 0x7 value confirms that Flash Encryption was enabled, and BLOCK_KEY1 - XTS_AES_128_KEY shows that the FE key is stored in the expected block. The board is also in Secure Download Mode. In this mode, downloading is limited to basic flashing commands. Data and instruction caches are disabled during download, and JTAG has been permanently disabled.
Flashing New Encrypted Firmware — Second or Later Flash
This procedure is for the second or subsequent flash, after the first firmware has already enabled Flash Encryption. After compiling the firmware, we'll merge the binaries into a single file:
### FIDO:
cd ~/src/picokeys-fe/pico-fido && mkdir -p release && cd build
esptool.py --chip ESP32-S3 merge_bin \
-o ../release/pico_fido_esp32-s3.bin \
--flash_mode dio --flash_freq 80m --flash_size keep \
0x0 bootloader/bootloader.bin \
0x10000 partition_table/partition-table.bin \
0x20000 pico_fido.bin && cd ..
### HSM:
cd ~/src/picokeys-fe/pico-hsm && mkdir -p release && cd build
esptool.py --chip ESP32-S3 merge_bin \
-o ../release/pico_hsm_esp32-s3.bin \
--flash_mode dio --flash_freq 80m --flash_size keep \
0x0 bootloader/bootloader.bin \
0x10000 partition_table/partition-table.bin \
0x20000 pico_hsm.bin && cd ..
Now let's encrypt the firmware:
# FIDO:
espsecure.py encrypt_flash_data --aes_xts \
--keyfile "/home/bruno/.config/picokeys/flash-encryption/pico-68ee8f51e038.bin" \
--address 0x0 \
--output release/pico_fido_esp32-s3.enc.bin \
release/pico_fido_esp32-s3.bin
# HSM:
espsecure.py encrypt_flash_data --aes_xts \
--keyfile "/home/bruno/.config/picokeys/flash-encryption/pico-68ee8f51e038.bin" \
--address 0x0 \
--output release/pico_hsm_esp32-s3.enc.bin \
release/pico_hsm_esp32-s3.bin
Before flashing, let's run a test to validate that encryption worked:
espsecure.py decrypt_flash_data --aes_xts \
--keyfile "/home/bruno/.config/picokeys/flash-encryption/pico-68ee8f51e038.bin" \
--address 0x0 \
--output /tmp/pico_hsm.dec.bin \
release/pico_hsm_esp32-s3.enc.bin
# Compare the original firmware (encrypted) with the newly decrypted firmware (if there is no output, everything matches):
cmp release/pico_hsm_esp32-s3.bin /tmp/pico_hsm.dec.bin
To flash, we need to use the --force option. Without it, the following happens:
$ esptool.py --chip ESP32-S3 -p "/dev/ttyACM0" --no-stub write_flash \
--flash_mode dio --flash_freq 80m --flash_size keep \
0x0 release/pico_hsm_esp32-s3.enc.bin
esptool.py v4.12.0
Serial port /dev/ttyACM0
Connecting...
Chip is ESP32-S3 in Secure Download Mode
Enabling default SPI flash mode...
Configuring flash size...
WARNING: In case of failure, please set a specific --flash_size.
A fatal error occurred: WARNING: Detected flash encryption and secure download mode enabled.
Flashing plaintext binary may brick your device! Use --force to override the warning.
This warning is expected from esptool because it detected a board with Flash Encryption and Secure Download Mode enabled. When these features are active, it cannot verify whether the file has been encrypted correctly, which is why we need --force.
Since we've validated the encryption, we can proceed:
# FIDO:
esptool.py --chip ESP32-S3 -p "/dev/ttyACM0" --no-stub write_flash --force \
--flash_mode dio --flash_freq 80m --flash_size keep \
0x0 release/pico_fido_esp32-s3.enc.bin
# HSM:
esptool.py --chip ESP32-S3 -p "/dev/ttyACM0" --no-stub write_flash --force \
--flash_mode dio --flash_freq 80m --flash_size keep \
0x0 release/pico_hsm_esp32-s3.enc.bin
Sources
https://docs.espressif.com/projects/esp-idf/en/stable/esp32s3/api-reference/peripherals/hmac.html
https://docs.espressif.com/projects/esp-idf/en/stable/esp32s3/security/secure-boot-v2.html
https://docs.espressif.com/projects/esp-idf/en/v5.2/esp32/api-reference/kconfig.html
https://docs.espressif.com/projects/esp-idf/en/v5.2/esp32s3/security/flash-encryption.html
https://docs.espressif.com/projects/esp-idf/en/v5.5/esp32s3/security/flash-encryption.html
https://docs.espressif.com/projects/esp-idf/en/v5.5/esp32s3/security/secure-boot-v2.html
https://docs.picokeys.com/picohsm/first-steps/
https://docs.picokeys.com/picohsm/key-generation/
https://docs.picokeys.com/picohsm/sign-verify/
https://docs.picokeys.com/picokeyapp/security/
https://docs.picokeys.com/production/otp-fuses
https://docs.picokeys.com/production/otp-fuses/
https://docs.picokeys.com/security/threat-model/
https://github.com/polhenarejos/pico-fido
https://github.com/polhenarejos/pico-hsm
https://github.com/polhenarejos/pico-hsm/pull/143
https://github.com/polhenarejos/pico-vault-enroller
https://www.picokeys.com/getting-started/
https://www.picokeys.com/pico-fido/
https://www.picokeys.com/pico-hsm/
https://www.picokeys.com/picokeyapp/
Top comments (0)