This commit is contained in:
2026-05-22 21:52:50 +03:00
commit be7c60e4dd
1854 changed files with 583428 additions and 0 deletions
@@ -0,0 +1,202 @@
# Signed OTA Update Example
This example demonstrates how to perform secure OTA (Over-The-Air) updates with signature verification on ESP32 devices using Arduino.
## Overview
Code signing ensures that only firmware signed with your private key will be accepted by your devices. This protects against unauthorized firmware updates, even if an attacker gains access to your update server.
## Features
- **RSA Signature Verification**: Supports RSA-2048, RSA-3072, and RSA-4096
- **ECDSA Signature Verification**: Supports ECDSA-P256 and ECDSA-P384
- **Multiple Hash Algorithms**: SHA-256, SHA-384, and SHA-512
- **Automatic Signature Verification**: Signatures are verified automatically during OTA update
- **Secure by Default**: Update fails if signature verification fails
## Prerequisites
1. **Python 3** with the `cryptography` package:
```bash
pip install cryptography
```
2. **ESP32 Arduino Core** with Update library
## Quick Start Guide
### Step 1: Generate Key Pair
Generate an RSA-2048 key pair (recommended):
```bash
python <ARDUINO_ROOT>/tools/bin_signing.py --generate-key rsa-2048 --out private_key.pem
python <ARDUINO_ROOT>/tools/bin_signing.py --extract-pubkey private_key.pem --out public_key.pem
```
Or generate an ECDSA-P256 key pair (smaller, faster):
```bash
python <ARDUINO_ROOT>/tools/bin_signing.py --generate-key ecdsa-p256 --out private_key.pem
python <ARDUINO_ROOT>/tools/bin_signing.py --extract-pubkey private_key.pem --out public_key.pem
```
Where `<ARDUINO_ROOT>` is your ESP32 Arduino installation path (e.g., `~/Arduino/hardware/espressif/esp32/`).
**IMPORTANT**: Keep `private_key.pem` secure! Anyone with access to it can sign firmware for your devices.
### Step 2: Update the Example Sketch
1. Copy the generated `public_key.h` to the example directory
2. Open `Signed_OTA_Update.ino`
3. Update Wi-Fi credentials:
```cpp
const char *ssid = "YOUR_SSID";
const char *password = "YOUR_PASSWORD";
```
4. Update firmware URL:
```cpp
const char *firmwareUrl = "http://your-server.com/firmware_signed.bin";
```
5. Uncomment the appropriate key type (RSA or ECDSA)
6. Uncomment the appropriate hash algorithm (SHA-256, SHA-384, or SHA-512)
### Step 3: Build and Upload Initial Firmware
1. Compile and upload the sketch to your ESP32
2. Open Serial Monitor to verify it's running
### Step 4: Build and Sign New Firmware
1. Make changes to your sketch (e.g., add a version number)
2. Build the sketch and export the binary:
- Arduino IDE: `Sketch` → `Export Compiled Binary`
- Find the application `.bin` file in the `build` folder of your sketch folder. For example `build/espressif.esp32.esp32c6/Signed_OTA_Update.ino.bin`.
3. Sign the binary:
```bash
python <ARDUINO_ROOT>/tools/bin_signing.py --bin <APPLICATION_BIN_FILE> --key private_key.pem --out firmware_signed.bin
```
For other hash algorithms (for example SHA-384):
```bash
python <ARDUINO_ROOT>/tools/bin_signing.py --bin <APPLICATION_BIN_FILE> --key private_key.pem --out firmware_signed.bin --hash sha384
```
### Step 5: Host the Signed Firmware
Upload `firmware_signed.bin` to your web server and make it accessible at the URL you configured.
### Step 6: Perform OTA Update
Reset your ESP32. It will:
1. Connect to Wi-Fi
2. Download the signed firmware
3. Verify the signature
4. Apply the update if signature is valid
5. Reboot with the new firmware
## Security Considerations
### Private Key Management
- **NEVER** commit your private key to version control
- Store it securely (encrypted storage, HSM, etc.)
- Limit access to authorized personnel only
- Consider using separate keys for development and production
### Recommended Practices
1. **Use HTTPS**: While signature verification protects firmware integrity, HTTPS protects against MitM attacks
2. **Key Rotation**: Periodically rotate keys (requires firmware update to include new public key)
## Signature Schemes Comparison
| Scheme | Key Size | Signature Size | Verification Speed | Security |
|--------|----------|----------------|-------------------|----------|
| RSA-2048 | 2048 bits | 256 bytes | Medium | High |
| RSA-3072 | 3072 bits | 384 bytes | Slower | Very High |
| RSA-4096 | 4096 bits | 512 bytes | Slowest | Maximum |
| ECDSA-P256 | 256 bits | 64 bytes | Fast | High |
| ECDSA-P384 | 384 bits | 96 bytes | Fast | Very High |
**Recommendation**: RSA-2048 or ECDSA-P256 provide good security with reasonable performance.
## Hash Algorithms Comparison
| Algorithm | Output Size | Speed | Security |
|-----------|-------------|-------|----------|
| SHA-256 | 32 bytes | Fast | High |
| SHA-384 | 48 bytes | Medium | Very High |
| SHA-512 | 64 bytes | Medium | Very High |
**Recommendation**: SHA-256 is sufficient for most applications.
## Troubleshooting
### "Signature verification failed"
- Ensure the firmware was signed with the correct private key
- Verify that the public key in the sketch matches the private key used for signing
- Check that the signature scheme (RSA/ECDSA) and hash algorithm match between signing and verification
- Ensure the signed binary wasn't corrupted during transfer
### "Failed to install signature verification"
- Check that `installSignature()` is called before `Update.begin()`
- Ensure hash and sign objects are properly initialized
### "Public key parsing failed"
- Verify the public key PEM format is correct
- Ensure PUBLIC_KEY_LEN matches the actual key length
## Advanced Usage
### Verifying a Signed Binary
You can verify a signed binary without flashing it:
```bash
python bin_signing.py --verify firmware_signed.bin --pubkey public_key.pem
```
### Using Different Hash Algorithms
Match the hash algorithm between signing and verification:
**Signing with SHA-384:**
```bash
python bin_signing.py --bin firmware.bin --key private_key.pem --out firmware_signed.bin --hash sha384
```
**Sketch configuration:**
```cpp
#define USE_SHA384
```
## API Reference
### Classes
- **UpdaterRSAVerifier**: RSA signature verifier
- **UpdaterECDSAVerifier**: ECDSA signature verifier
### Methods
```cpp
// Install signature verification (call before Update.begin())
bool Update.installSignature(UpdaterVerifyClass *sign);
```
### Error Codes
- `UPDATE_ERROR_SIGN (14)`: Signature verification failed
## License
This example is part of the Arduino-ESP32 project and is licensed under the Apache License 2.0.
## Support
For issues and questions:
- GitHub: https://github.com/espressif/arduino-esp32/issues
- Documentation: https://docs.espressif.com/
@@ -0,0 +1,231 @@
/*
Signed OTA Update Example
This example demonstrates how to perform a secure OTA update with signature verification.
Only firmware signed with the correct private key will be accepted.
NOTE: This example requires signature verification support to be enabled.
This is done automatically via the build_opt.h file in this directory.
Steps to use this example:
1. Generate a key pair (see instructions below)
2. Include the public key in this sketch (see public_key.h)
3. Build and upload this sketch to your ESP32
4. Build your new firmware binary
5. Sign the binary with the private key (see instructions below)
6. Upload the signed firmware via OTA (HTTP/HTTPS server)
Generating keys:
------------------
RSA (recommended for maximum compatibility):
python bin_signing.py --generate-key rsa-2048 --out private_key.pem
python bin_signing.py --extract-pubkey private_key.pem --out public_key.pem
ECDSA (smaller keys, faster verification):
python bin_signing.py --generate-key ecdsa-p256 --out private_key.pem
python bin_signing.py --extract-pubkey private_key.pem --out public_key.pem
Signing firmware:
-----------------
python bin_signing.py --bin firmware.bin --key private_key.pem --out firmware_signed.bin
IMPORTANT: Keep your private_key.pem secure! Anyone with access to it can
sign firmware that will be accepted by your devices.
Created by lucasssvaz
*/
#include <WiFi.h>
#include <HTTPClient.h>
#include <Update.h>
#include <SHA2Builder.h>
// WiFi credentials
const char *ssid = "YOUR_SSID";
const char *password = "YOUR_PASSWORD";
// URL to the signed firmware binary
const char *firmwareUrl = "http://your-server.com/firmware_signed.bin";
// Public key for signature verification
// Generated with: python bin_signing.py --extract-pubkey private_key.pem --out public_key.pem
// This will create a public_key.h file that you should include below
#include "public_key.h"
// Uncomment the key type you're using:
#define USE_RSA // RSA signature verification
//#define USE_ECDSA // ECDSA signature verification
// Uncomment the hash algorithm you're using (must match the one used for signing):
#define USE_SHA256 // SHA-256 (recommended and default)
//#define USE_SHA384 // SHA-384
//#define USE_SHA512 // SHA-512
void performOTAUpdate() {
HTTPClient http;
Serial.println("Starting OTA update...");
Serial.print("Firmware URL: ");
Serial.println(firmwareUrl);
http.begin(firmwareUrl);
int httpCode = http.GET();
if (httpCode != HTTP_CODE_OK) {
Serial.printf("HTTP GET failed, error: %s\n", http.errorToString(httpCode).c_str());
http.end();
return;
}
int contentLength = http.getSize();
Serial.printf("Firmware size: %d bytes\n", contentLength);
if (contentLength <= 0) {
Serial.println("Invalid content length");
http.end();
return;
}
// The signed firmware includes the signature (512 bytes padding)
// The actual firmware size is contentLength - 512
const size_t signatureSize = 512;
size_t firmwareSize = contentLength - signatureSize;
Serial.printf("Actual firmware size: %zu bytes\n", firmwareSize);
Serial.printf("Signature size: %zu bytes\n", signatureSize);
// Select hash algorithm
#ifdef USE_SHA256
int hashType = HASH_SHA256;
Serial.println("Using SHA-256 hash");
#elif defined(USE_SHA384)
int hashType = HASH_SHA384;
Serial.println("Using SHA-384 hash");
#elif defined(USE_SHA512)
int hashType = HASH_SHA512;
Serial.println("Using SHA-512 hash");
#else
#error "Please define a hash algorithm (USE_SHA256, USE_SHA384, or USE_SHA512)"
#endif
// Create verifier object
#ifdef USE_RSA
UpdaterRSAVerifier sign(PUBLIC_KEY, PUBLIC_KEY_LEN, hashType);
Serial.println("Using RSA signature verification");
#elif defined(USE_ECDSA)
UpdaterECDSAVerifier sign(PUBLIC_KEY, PUBLIC_KEY_LEN, hashType);
Serial.println("Using ECDSA signature verification");
#else
#error "Please define a signature scheme (USE_RSA or USE_ECDSA)"
#endif
// Install signature verification BEFORE calling Update.begin()
if (!Update.installSignature(&sign)) {
Serial.println("Failed to install signature verification");
http.end();
return;
}
Serial.println("Signature verification installed");
// Begin update with the TOTAL size (firmware + signature)
if (!Update.begin(contentLength)) {
Serial.printf("Update.begin failed: %s\n", Update.errorString());
http.end();
return;
}
// Get the stream
WiFiClient *stream = http.getStreamPtr();
// Write firmware data
Serial.println("Writing firmware...");
size_t written = 0;
uint8_t buff[1024];
int progress = 0;
while (http.connected() && (written < contentLength)) {
size_t available = stream->available();
if (available) {
int bytesRead = stream->readBytes(buff, min(available, sizeof(buff)));
if (bytesRead > 0) {
size_t bytesWritten = Update.write(buff, bytesRead);
if (bytesWritten > 0) {
written += bytesWritten;
// Print progress
int newProgress = (written * 100) / contentLength;
if (newProgress != progress && newProgress % 10 == 0) {
progress = newProgress;
Serial.printf("Progress: %d%%\n", progress);
}
} else {
Serial.printf("Update.write failed: %s\n", Update.errorString());
break;
}
}
}
delay(1);
}
Serial.printf("Written: %zu bytes\n", written);
// End the update - this will verify the signature
if (Update.end()) {
Serial.println("OTA update completed successfully!");
Serial.println("Signature verified!");
if (Update.isFinished()) {
Serial.println("Update successfully completed. Rebooting...");
delay(1000);
ESP.restart();
} else {
Serial.println("Update not finished? Something went wrong!");
}
} else {
Serial.printf("Update.end failed: %s\n", Update.errorString());
// Check if it was a signature verification failure
if (Update.getError() == UPDATE_ERROR_SIGN) {
Serial.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
Serial.println("SIGNATURE VERIFICATION FAILED!");
Serial.println("The firmware was not signed with the");
Serial.println("correct private key or is corrupted.");
Serial.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
}
}
http.end();
}
void setup() {
Serial.begin(115200);
Serial.println("\n\nSigned OTA Update Example");
Serial.println("=========================\n");
// Connect to WiFi
Serial.printf("Connecting to WiFi: %s\n", ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi connected!");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
// Wait a bit before starting OTA
delay(2000);
// Perform OTA update
performOTAUpdate();
}
void loop() {
// Nothing to do here
delay(1000);
}
@@ -0,0 +1 @@
-DUPDATE_SIGN
@@ -0,0 +1,3 @@
requires_any:
- CONFIG_SOC_WIFI_SUPPORTED=y
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
@@ -0,0 +1,32 @@
// Public key for OTA signature verification
// Include this in your Arduino sketch
// ⚠️ THIS IS A TEST KEY - DO NOT USE IN PRODUCTION!
// Generate your own keys using:
// python <ARDUINO_ROOT>/tools/bin_signing.py --generate-key rsa-2048 --out private_key.pem
// python <ARDUINO_ROOT>/tools/bin_signing.py --extract-pubkey private_key.pem --out public_key.pem
//
// Then replace this file with the generated public_key.h
// Test RSA-2048 Public Key (PEM format)
const uint8_t PUBLIC_KEY[] PROGMEM = {
0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x4b, 0x45, 0x59, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
0x0a, 0x4d, 0x49, 0x49, 0x42, 0x49, 0x6a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x45, 0x46, 0x41,
0x41, 0x4f, 0x43, 0x41, 0x51, 0x38, 0x41, 0x4d, 0x49, 0x49, 0x42, 0x43, 0x67, 0x4b, 0x43, 0x41, 0x51, 0x45, 0x41, 0x36, 0x42, 0x33, 0x52, 0x67, 0x34, 0x39,
0x6b, 0x4e, 0x47, 0x72, 0x44, 0x2b, 0x50, 0x46, 0x6e, 0x39, 0x64, 0x69, 0x4b, 0x0a, 0x57, 0x50, 0x34, 0x65, 0x42, 0x59, 0x4d, 0x2f, 0x49, 0x79, 0x6b, 0x55,
0x4b, 0x4d, 0x34, 0x39, 0x63, 0x6a, 0x65, 0x56, 0x56, 0x4f, 0x39, 0x42, 0x4f, 0x30, 0x66, 0x6c, 0x47, 0x47, 0x6e, 0x47, 0x71, 0x79, 0x34, 0x50, 0x72, 0x69,
0x4e, 0x71, 0x32, 0x62, 0x4a, 0x4a, 0x6a, 0x7a, 0x68, 0x38, 0x46, 0x32, 0x42, 0x53, 0x4f, 0x75, 0x74, 0x48, 0x77, 0x75, 0x7a, 0x6d, 0x62, 0x45, 0x52, 0x6f,
0x0a, 0x30, 0x38, 0x51, 0x72, 0x32, 0x30, 0x4e, 0x61, 0x52, 0x72, 0x7a, 0x6e, 0x71, 0x6e, 0x59, 0x4e, 0x57, 0x4e, 0x69, 0x6e, 0x43, 0x67, 0x7a, 0x34, 0x34,
0x49, 0x4e, 0x50, 0x50, 0x78, 0x70, 0x45, 0x55, 0x65, 0x68, 0x61, 0x32, 0x66, 0x6d, 0x6d, 0x39, 0x77, 0x5a, 0x67, 0x57, 0x31, 0x69, 0x31, 0x67, 0x31, 0x77,
0x70, 0x68, 0x56, 0x51, 0x6c, 0x5a, 0x30, 0x49, 0x63, 0x72, 0x6d, 0x5a, 0x5a, 0x0a, 0x42, 0x61, 0x33, 0x49, 0x64, 0x6a, 0x78, 0x63, 0x52, 0x67, 0x51, 0x6c,
0x69, 0x32, 0x4b, 0x74, 0x78, 0x72, 0x41, 0x4a, 0x67, 0x33, 0x4a, 0x47, 0x43, 0x54, 0x2f, 0x39, 0x6d, 0x7a, 0x52, 0x31, 0x70, 0x37, 0x59, 0x34, 0x50, 0x34,
0x65, 0x71, 0x30, 0x6b, 0x2b, 0x78, 0x2b, 0x45, 0x72, 0x6f, 0x35, 0x73, 0x47, 0x69, 0x49, 0x7a, 0x33, 0x44, 0x67, 0x61, 0x50, 0x43, 0x54, 0x41, 0x37, 0x52,
0x0a, 0x4b, 0x69, 0x75, 0x6e, 0x2f, 0x67, 0x64, 0x56, 0x71, 0x34, 0x35, 0x2f, 0x75, 0x62, 0x64, 0x53, 0x58, 0x65, 0x62, 0x50, 0x46, 0x43, 0x73, 0x36, 0x66,
0x46, 0x73, 0x52, 0x39, 0x6d, 0x43, 0x6f, 0x37, 0x70, 0x43, 0x4b, 0x74, 0x45, 0x55, 0x51, 0x78, 0x34, 0x4d, 0x68, 0x55, 0x4e, 0x5a, 0x48, 0x48, 0x31, 0x49,
0x33, 0x62, 0x79, 0x57, 0x35, 0x7a, 0x39, 0x36, 0x49, 0x6a, 0x46, 0x44, 0x68, 0x0a, 0x54, 0x2f, 0x64, 0x5a, 0x71, 0x32, 0x6d, 0x44, 0x54, 0x64, 0x76, 0x59,
0x2b, 0x6d, 0x5a, 0x75, 0x51, 0x4d, 0x37, 0x6c, 0x72, 0x31, 0x4d, 0x4e, 0x6a, 0x35, 0x36, 0x79, 0x74, 0x41, 0x56, 0x4a, 0x39, 0x56, 0x7a, 0x74, 0x44, 0x75,
0x35, 0x4f, 0x6a, 0x48, 0x32, 0x76, 0x6f, 0x32, 0x6b, 0x59, 0x46, 0x4f, 0x72, 0x52, 0x49, 0x57, 0x70, 0x5a, 0x4c, 0x56, 0x35, 0x6c, 0x47, 0x79, 0x7a, 0x45,
0x0a, 0x33, 0x77, 0x49, 0x44, 0x41, 0x51, 0x41, 0x42, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20,
0x4b, 0x45, 0x59, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x00,
};
const size_t PUBLIC_KEY_LEN = 452;