-
Notifications
You must be signed in to change notification settings - Fork 0
Add ESP-IDF Secure Boot v2 support #130
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
7b5ebc1
Add ESP-IDF Secure Boot v2 support
wksantiago 4ad3a01
fix: address security review feedback
kwsantiago 61d0e8a
refactor: simplify signing script and fix docs
kwsantiago 6bd6f55
docs: document ROM download mode options in secureboot config
kwsantiago File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| # Secure Boot v2 | ||
|
|
||
| ESP-IDF Secure Boot v2 implementation for hardware wallet firmware verification. | ||
|
|
||
| ## Overview | ||
|
|
||
| Secure Boot v2 provides: | ||
| - RSA-3072/PSS signature verification of bootloader and application | ||
| - Hardware-enforced boot chain verification | ||
| - Anti-rollback protection via eFuse version counter | ||
| - Optional flash encryption for data at rest | ||
|
|
||
| ## Quick Start | ||
|
|
||
| ### 1. Generate Signing Key | ||
|
|
||
| ```bash | ||
| ./scripts/sign_firmware.sh generate-key | ||
| ``` | ||
|
|
||
| This creates `keys/secure_boot_signing_key.pem` (RSA-3072 private key). | ||
|
|
||
| ### 2. Build with Secure Boot | ||
|
|
||
| ```bash | ||
| idf.py -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.secureboot" build | ||
| ``` | ||
|
|
||
| ### 3. Sign Firmware | ||
|
|
||
| ```bash | ||
| ./scripts/sign_firmware.sh sign-all | ||
| ``` | ||
|
|
||
| ### 4. Flash to Device (First Time) | ||
|
|
||
| First boot burns the public key hash to eFuse: | ||
|
|
||
| ```bash | ||
| esptool.py --chip esp32s3 --port /dev/ttyACM0 write_flash \ | ||
| 0x0 build/bootloader/bootloader-signed.bin \ | ||
| 0xc000 build/partition_table/partition-table.bin \ | ||
| 0x20000 build/keep-signed.bin | ||
| ``` | ||
|
|
||
| ## Key Management | ||
|
|
||
| ### Security Requirements | ||
|
|
||
| - Store the signing key offline in multiple secure locations | ||
| - Use hardware security modules (HSM) for production | ||
| - Never commit keys to version control | ||
| - Loss of key = inability to update firmware on locked devices | ||
|
|
||
| ### Key Backup Procedure | ||
|
|
||
| 1. Generate key on air-gapped machine | ||
| 2. Create encrypted backups to multiple USB drives | ||
| 3. Store in geographically separate secure locations | ||
| 4. Test recovery procedure before production deployment | ||
|
|
||
| ### Production Workflow | ||
|
|
||
| For production builds: | ||
|
|
||
| 1. CI builds unsigned firmware | ||
| 2. Signing occurs on secure, offline workstation with HSM | ||
| 3. Signed binaries uploaded to release | ||
|
|
||
| ## Anti-Rollback | ||
|
|
||
| The `CONFIG_BOOTLOADER_APP_SECURE_VERSION` setting tracks firmware version in eFuse. The bootloader refuses to boot firmware with a lower version number. | ||
|
|
||
| To increment version for a security-critical update: | ||
|
|
||
| ``` | ||
| CONFIG_BOOTLOADER_APP_SECURE_VERSION=2 | ||
| ``` | ||
|
|
||
| Each increment burns an eFuse bit. ESP32-S3 supports 32 version increments. | ||
|
|
||
| ## Flash Encryption (Optional) | ||
|
|
||
| For additional protection of firmware at rest, enable flash encryption in `sdkconfig.defaults.secureboot`: | ||
|
|
||
| ``` | ||
| CONFIG_SECURE_FLASH_ENC_ENABLED=y | ||
| CONFIG_SECURE_FLASH_ENCRYPTION_MODE_RELEASE=y | ||
| ``` | ||
|
|
||
| Considerations: | ||
| - Encryption key burned to eFuse on first boot | ||
| - Cannot read flash contents without key | ||
| - Increases boot time slightly | ||
| - Requires all flash contents to be encrypted | ||
|
|
||
| ## Verification | ||
|
|
||
| Verify signatures before flashing: | ||
|
|
||
| ```bash | ||
| ./scripts/sign_firmware.sh verify | ||
| ``` | ||
|
|
||
| Verify eFuse configuration on device: | ||
|
|
||
| ```bash | ||
| espefuse.py --port /dev/ttyACM0 summary | ||
| ``` | ||
|
|
||
| ## Recovery | ||
|
|
||
| If secure boot is enabled and the signing key is lost: | ||
| - Device cannot be updated | ||
| - No recovery possible | ||
| - This is by design for security | ||
|
|
||
| If bootloader is corrupted but key is available: | ||
| - Reflash signed bootloader via UART (if not disabled) | ||
| - Or use USB DFU if supported | ||
|
|
||
| ## CI Integration | ||
|
|
||
| For automated builds without secure boot (development): | ||
|
|
||
| ```yaml | ||
| - name: Build firmware | ||
| run: idf.py build | ||
| ``` | ||
|
|
||
| For signed release builds: | ||
|
|
||
| ```yaml | ||
| - name: Build firmware (unsigned) | ||
| run: idf.py -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.secureboot" build | ||
|
|
||
| - name: Sign firmware | ||
| run: ./scripts/sign_firmware.sh sign-all | ||
| env: | ||
| SIGNING_KEY: ${{ secrets.SECURE_BOOT_KEY_PATH }} | ||
| ``` | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| ### "Secure boot key not found" | ||
|
|
||
| Ensure key exists at `keys/secure_boot_signing_key.pem` or set `SIGNING_KEY` environment variable. | ||
|
|
||
| ### "Signature verification failed" | ||
|
|
||
| - Key mismatch: firmware signed with different key than device expects | ||
| - Corrupted binary: re-download or rebuild | ||
| - Wrong version: anti-rollback may prevent older firmware | ||
|
|
||
| ### "eFuse already programmed" | ||
|
|
||
| Secure boot configuration is permanent. The device is locked to the programmed key. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| # Name, Type, SubType, Offset, Size, Flags | ||
| nvs, data, nvs, 0xd000, 0x3000, | ||
| otadata, data, ota, 0x10000, 0x2000, | ||
| phy_init, data, phy, 0x12000, 0x1000, | ||
| factory, app, factory, 0x20000, 0x2F0000, | ||
| storage, data, 0x40, 0x310000, 0x10000, | ||
| policy, data, 0x41, 0x320000, 0x1000, | ||
| checkpoint, data, 0x42, 0x321000, 0x7000, |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| #!/bin/bash | ||
| set -e | ||
|
|
||
| SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" | ||
| PROJECT_DIR="$(dirname "$SCRIPT_DIR")" | ||
| BUILD_DIR="${PROJECT_DIR}/build" | ||
| KEYS_DIR="${PROJECT_DIR}/keys" | ||
| SIGNING_KEY="${SIGNING_KEY:-${KEYS_DIR}/secure_boot_signing_key.pem}" | ||
|
|
||
| usage() { | ||
| echo "Usage: $0 <command>" | ||
| echo "" | ||
| echo "Commands:" | ||
| echo " generate-key Generate new RSA-3072 signing key" | ||
| echo " sign-app Sign application binary" | ||
| echo " sign-bootloader Sign bootloader binary" | ||
| echo " sign-all Sign both bootloader and application" | ||
| echo " verify Verify signatures on built binaries" | ||
| echo "" | ||
| echo "Environment:" | ||
| echo " SIGNING_KEY Path to signing key (default: keys/secure_boot_signing_key.pem)" | ||
| exit 1 | ||
| } | ||
|
|
||
| check_espsecure() { | ||
| if ! command -v espsecure.py &> /dev/null; then | ||
| echo "Error: espsecure.py not found. Ensure ESP-IDF is activated." | ||
| exit 1 | ||
| fi | ||
| } | ||
|
|
||
| check_signing_key() { | ||
| if [ ! -f "$SIGNING_KEY" ]; then | ||
| echo "Error: Signing key not found at $SIGNING_KEY" | ||
| echo "Run '$0 generate-key' first." | ||
| exit 1 | ||
| fi | ||
| } | ||
|
|
||
| generate_key() { | ||
| check_espsecure | ||
| mkdir -p "$KEYS_DIR" | ||
|
|
||
| if [ -f "$SIGNING_KEY" ]; then | ||
| echo "Error: Key already exists at $SIGNING_KEY" | ||
| echo "Remove it first if you want to generate a new key." | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "Generating RSA-3072 signing key..." | ||
| (umask 077; espsecure.py generate_signing_key --version 2 "$SIGNING_KEY") | ||
|
|
||
| echo "Key generated: $SIGNING_KEY" | ||
| echo "CRITICAL: Back up this key securely. Loss means inability to update firmware." | ||
| } | ||
|
|
||
| sign_app() { | ||
| check_espsecure | ||
| check_signing_key | ||
|
|
||
| local app_bin="${BUILD_DIR}/keep.bin" | ||
| local signed_bin="${BUILD_DIR}/keep-signed.bin" | ||
|
|
||
| if [ ! -f "$app_bin" ]; then | ||
| echo "Error: Application binary not found at $app_bin" | ||
| echo "Build the project first with: idf.py build" | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "Signing application..." | ||
| espsecure.py sign_data --version 2 --keyfile "$SIGNING_KEY" \ | ||
| --output "$signed_bin" "$app_bin" | ||
|
|
||
| echo "Signed application: $signed_bin" | ||
| } | ||
|
|
||
| sign_bootloader() { | ||
| check_espsecure | ||
| check_signing_key | ||
|
|
||
| local bootloader_bin="${BUILD_DIR}/bootloader/bootloader.bin" | ||
| local signed_bin="${BUILD_DIR}/bootloader/bootloader-signed.bin" | ||
|
|
||
| if [ ! -f "$bootloader_bin" ]; then | ||
| echo "Error: Bootloader binary not found at $bootloader_bin" | ||
| echo "Build the project first with: idf.py build" | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "Signing bootloader..." | ||
| espsecure.py sign_data --version 2 --keyfile "$SIGNING_KEY" \ | ||
| --output "$signed_bin" "$bootloader_bin" | ||
|
|
||
| echo "Signed bootloader: $signed_bin" | ||
| } | ||
|
|
||
| verify_signatures() { | ||
| check_espsecure | ||
|
|
||
| local app_bin="${BUILD_DIR}/keep-signed.bin" | ||
| local bootloader_bin="${BUILD_DIR}/bootloader/bootloader-signed.bin" | ||
|
|
||
| echo "Verifying signatures..." | ||
|
|
||
| if [ -f "$app_bin" ]; then | ||
| echo "Verifying application..." | ||
| espsecure.py verify_signature --version 2 --keyfile "$SIGNING_KEY" "$app_bin" | ||
| else | ||
| echo "Warning: Signed application not found" | ||
| fi | ||
|
|
||
| if [ -f "$bootloader_bin" ]; then | ||
| echo "Verifying bootloader..." | ||
| espsecure.py verify_signature --version 2 --keyfile "$SIGNING_KEY" "$bootloader_bin" | ||
| else | ||
| echo "Warning: Signed bootloader not found" | ||
| fi | ||
|
|
||
| echo "Verification complete." | ||
| } | ||
|
|
||
| case "${1:-}" in | ||
| generate-key) | ||
| generate_key | ||
| ;; | ||
| sign-app) | ||
| sign_app | ||
| ;; | ||
| sign-bootloader) | ||
| sign_bootloader | ||
| ;; | ||
| sign-all) | ||
| sign_bootloader | ||
| sign_app | ||
| ;; | ||
| verify) | ||
| verify_signatures | ||
| ;; | ||
| *) | ||
| usage | ||
| ;; | ||
| esac |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| # Secure Boot v2 for ESP32-S3 | ||
| CONFIG_SECURE_BOOT=y | ||
| CONFIG_SECURE_BOOT_V2_ENABLED=y | ||
| CONFIG_SECURE_SIGNED_ON_BOOT=y | ||
| CONFIG_SECURE_SIGNED_APPS=y | ||
| CONFIG_SECURE_SIGNED_APPS_RSA_SCHEME=y | ||
| CONFIG_SECURE_BOOT_SIGNING_KEY="keys/secure_boot_signing_key.pem" | ||
| CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES=n | ||
|
|
||
| CONFIG_PARTITION_TABLE_CUSTOM=y | ||
| # 0xc000 offset (vs default 0x8000) accommodates larger signed bootloader | ||
| CONFIG_PARTITION_TABLE_OFFSET=0xc000 | ||
| CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.secureboot.csv" | ||
|
|
||
| CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y | ||
| CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK=y | ||
| CONFIG_BOOTLOADER_APP_SECURE_VERSION=1 | ||
|
|
||
| # Flash encryption disabled: firmware already protects secrets with AES-256-GCM. | ||
| # Enabling adds complexity (encrypted OTA, recovery difficulty) with limited | ||
| # additional security benefit for this use case. Enable if threat model requires | ||
| # protection against flash readout attacks. | ||
| # CONFIG_SECURE_FLASH_ENC_ENABLED=y | ||
| # CONFIG_SECURE_FLASH_ENCRYPTION_MODE_RELEASE=y | ||
|
|
||
| # ROM Download Mode options (mutually exclusive, choose ONE): | ||
| # - DISABLE: Burns eFuse to permanently disable ROM download. Most secure but no recovery. | ||
| # - INSECURE_ALLOW: Keeps full esptool/ROM download access. Use for development only. | ||
| # - SECURE_ROM_DL: Enables secure download mode with restrictions. Middle ground if supported. | ||
| CONFIG_SECURE_DISABLE_ROM_DL_MODE=y | ||
| # CONFIG_SECURE_INSECURE_ALLOW_DL_MODE=y | ||
| # CONFIG_SECURE_ENABLE_SECURE_ROM_DL_MODE=y | ||
|
|
||
| CONFIG_SECURE_BOOT_FLASH_BOOTLOADER_DEFAULT=n | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.