Files
2026-05-22 21:52:50 +03:00

432 lines
11 KiB
Markdown

# ESP32 Arduino Update Library
The Update library provides functionality for Over-The-Air (OTA) firmware updates on ESP32 devices. It supports secure updates with signature verification, encrypted updates, and various update sources.
## Features
- **OTA Updates**: Update firmware over Wi-Fi
- **Signature Verification**: RSA and ECDSA signature verification for secure updates (optional, must be enabled with `UPDATE_SIGN`)
- **Image Encryption**: Support for encrypted firmware updates (optional, can be disabled with `UPDATE_NOCRYPT`)
- **Multiple Sources**: HTTP, HTTPS, SD card, and custom sources
- **Progress Callbacks**: Monitor update progress
- **MD5 Verification**: Optional MD5 checksum verification
## Quick Start
### Basic OTA Update
```cpp
#include <Update.h>
WiFiClient client;
size_t updateSize = client.available();
if (Update.begin(updateSize)) {
Update.writeStream(client);
if (Update.end()) {
Serial.println("Update successful!");
ESP.restart();
} else {
Serial.println("Update failed!");
}
}
```
### Signed OTA Update (Recommended)
To enable signature verification, add `-DUPDATE_SIGN` to your build flags (e.g., in `build_opt.h`):
```
-DUPDATE_SIGN
```
Then in your sketch:
```cpp
#include <Update.h>
// Include your public key (generated with bin_signing.py)
#include "public_key.h"
// Create verifier object (defaults to SHA-256)
UpdaterRSAVerifier sign(PUBLIC_KEY, PUBLIC_KEY_LEN);
// Install signature verification BEFORE Update.begin()
Update.installSignature(&sign);
// Now perform the update as usual
if (Update.begin(updateSize)) {
Update.writeStream(client);
if (Update.end()) {
// Signature was verified successfully!
Serial.println("Signed update successful!");
ESP.restart();
} else {
if (Update.getError() == UPDATE_ERROR_SIGN) {
Serial.println("Signature verification failed!");
}
}
}
```
## Signature Verification
### Overview
Code signing ensures that only firmware signed with your private key will be accepted by your devices. This protects against:
- Unauthorized firmware updates
- Man-in-the-middle attacks
- Compromised update servers
- Supply chain attacks
### Supported Algorithms
**Signature Schemes:**
- RSA-2048, RSA-3072, RSA-4096
- ECDSA-P256, ECDSA-P384
**Hash Algorithms:**
- SHA-256, SHA-384, SHA-512
### Setup
1. **Generate Key Pair:**
```bash
# RSA-2048 (recommended)
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
# ECDSA-P256 (smaller, faster)
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
```
2. **Include Public Key in Sketch:**
```cpp
#include "public_key.h" // Generated by bin_signing.py
```
3. **Install Signature Verification:**
Enable the feature by adding to `build_opt.h`:
```
-DUPDATE_SIGN
```
Then in your sketch:
```cpp
// For RSA with SHA-256
UpdaterRSAVerifier sign(PUBLIC_KEY, PUBLIC_KEY_LEN, HASH_SHA256);
Update.installSignature(&sign);
// For ECDSA with SHA-384
UpdaterECDSAVerifier sign(PUBLIC_KEY, PUBLIC_KEY_LEN, HASH_SHA384);
Update.installSignature(&sign);
```
4. **Sign Your Firmware:**
```bash
python <ARDUINO_ROOT>/tools/bin_signing.py --bin <APPLICATION_BIN_FILE> --key private_key.pem --out firmware_signed.bin --hash <HASH_ALGORITHM>
```
5. **Upload Signed Application Firmware:**
The signed firmware includes the signature appended to the binary. Upload the newly created signed firmware instead of the original application binary.
### Security Best Practices
1. **Protect Your Private Key:**
- Never commit it to version control
- Store it in secure, encrypted storage
- Limit access to authorized personnel only
- Consider using HSM for production
2. **Use HTTPS:**
- While signature verification protects integrity, HTTPS protects confidentiality
## API Reference
### UpdateClass Methods
#### begin()
```cpp
bool begin(size_t size = UPDATE_SIZE_UNKNOWN,
int command = U_FLASH,
int ledPin = -1,
uint8_t ledOn = LOW,
const char *label = NULL)
```
Starts an update operation.
**Parameters:**
- `size`: Size of the update in bytes (including signature if using signed updates)
- `command`: Update type (U_FLASH, U_SPIFFS, U_FATFS, U_LITTLEFS)
- `ledPin`: Optional LED pin to indicate progress
- `ledOn`: LED on state (LOW or HIGH)
- `label`: Optional partition label
**Returns:** `true` on success, `false` on failure
#### installSignature()
```cpp
bool installSignature(UpdaterVerifyClass *sign)
```
Installs signature verification. Must be called before `begin()`.
**Parameters:**
- `sign`: Signature verifier (UpdaterRSAVerifier or UpdaterECDSAVerifier)
**Returns:** `true` on success, `false` on failure
#### write()
```cpp
size_t write(uint8_t *data, size_t len)
```
Writes data to the update.
**Parameters:**
- `data`: Data buffer
- `len`: Length of data
**Returns:** Number of bytes written
#### writeStream()
```cpp
size_t writeStream(Stream &data)
```
Writes data from a stream.
**Parameters:**
- `data`: Input stream
**Returns:** Number of bytes written
#### end()
```cpp
bool end(bool evenIfRemaining = false)
```
Completes the update and verifies signature if enabled.
**Parameters:**
- `evenIfRemaining`: Complete even if not all data was written
**Returns:** `true` if update succeeded and signature is valid, `false` otherwise
#### abort()
```cpp
void abort()
```
Aborts the current update.
#### setMD5()
```cpp
bool setMD5(const char *expected_md5)
```
Sets expected MD5 hash for verification.
**Parameters:**
- `expected_md5`: MD5 hash as hex string (32 characters)
**Returns:** `true` on success, `false` on failure
#### getError()
```cpp
uint8_t getError()
```
Returns the last error code.
**Returns:** Error code (see Error Codes below)
#### errorString()
```cpp
const char *errorString()
```
Returns a human-readable error message.
**Returns:** Error message string
### Hash Classes (from Hash Library)
The Update library uses the Hash library for hashing. Simply use the builders from that library:
```cpp
#include <SHA2Builder.h>
SHA256Builder hash256; // SHA-256
SHA384Builder hash384; // SHA-384
SHA512Builder hash512; // SHA-512
```
See the [Hash library documentation](../Hash/README.md) for more details.
### Signature Verifier Classes
#### UpdaterRSAVerifier
RSA signature verifier.
```cpp
UpdaterRSAVerifier(const uint8_t *pubkey, size_t pubkeyLen, int hashType = HASH_SHA256)
```
**Parameters:**
- `pubkey`: Public key in PEM format
- `pubkeyLen`: Length of public key
- `hashType`: Hash algorithm (`HASH_SHA256`, `HASH_SHA384`, or `HASH_SHA512`). Defaults to `HASH_SHA256`.
#### UpdaterECDSAVerifier
ECDSA signature verifier.
```cpp
UpdaterECDSAVerifier(const uint8_t *pubkey, size_t pubkeyLen, int hashType = HASH_SHA256)
```
**Parameters:**
- `pubkey`: Public key in PEM format
- `pubkeyLen`: Length of public key
- `hashType`: Hash algorithm (`HASH_SHA256`, `HASH_SHA384`, or `HASH_SHA512`). Defaults to `HASH_SHA256`.
### Error Codes
| Code | Name | Description |
|------|------|-------------|
| 0 | UPDATE_ERROR_OK | No error |
| 1 | UPDATE_ERROR_WRITE | Flash write failed |
| 2 | UPDATE_ERROR_ERASE | Flash erase failed |
| 3 | UPDATE_ERROR_READ | Flash read failed |
| 4 | UPDATE_ERROR_SPACE | Not enough space |
| 5 | UPDATE_ERROR_SIZE | Bad size given |
| 6 | UPDATE_ERROR_STREAM | Stream read timeout |
| 7 | UPDATE_ERROR_MD5 | MD5 check failed |
| 8 | UPDATE_ERROR_MAGIC_BYTE | Wrong magic byte |
| 9 | UPDATE_ERROR_ACTIVATE | Could not activate firmware |
| 10 | UPDATE_ERROR_NO_PARTITION | Partition not found |
| 11 | UPDATE_ERROR_BAD_ARGUMENT | Bad argument |
| 12 | UPDATE_ERROR_ABORT | Aborted |
| 13 | UPDATE_ERROR_DECRYPT | Decryption error |
| 14 | UPDATE_ERROR_SIGN | Signature verification failed |
## Examples
- **Signed_OTA_Update**: Demonstrates signed OTA updates with RSA/ECDSA
- **HTTPS_OTA_Update**: HTTPS OTA update
- **HTTP_Client_AES_OTA_Update**: Encrypted OTA update
- **SD_Update**: Update from SD card
See the `examples/` directory for complete examples.
## Tools
### bin_signing.py
Python script for key generation and firmware signing. Located in `<ARDUINO_ROOT>/tools/bin_signing.py`.
**Requirements:**
```bash
pip install cryptography
```
**Usage:**
```bash
# Generate keys
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
# Sign firmware (defaults to SHA-256)
python <ARDUINO_ROOT>/tools/bin_signing.py --bin firmware.bin --key private_key.pem --out firmware_signed.bin
# Sign firmware with SHA-384
python <ARDUINO_ROOT>/tools/bin_signing.py --bin firmware.bin --key private_key.pem --out firmware_signed.bin --hash sha384
# Verify signature
python <ARDUINO_ROOT>/tools/bin_signing.py --verify firmware_signed.bin --pubkey public_key.pem
```
See `<ARDUINO_ROOT>/tools/bin_signing.py --help` for more options.
## Troubleshooting
### "Signature verification failed"
- Ensure firmware was signed with correct private key
- Verify public key in sketch matches private key
- Check signature scheme and hash algorithm match
- Verify signed binary wasn't corrupted
### "Failed to install signature verification"
- Call `installSignature()` before `Update.begin()`
- Ensure hash and sign objects are properly initialized
### "Update failed" with no specific error
- Check firmware size is correct (including signature)
- Ensure enough space in target partition
- Verify magic byte (0xE9) at start of firmware
### Memory Issues
- Signature verification requires ~2KB of heap
- RSA-4096 uses more memory than ECDSA-P256
- Ensure sufficient free heap before starting update
## Compile-Time Options
The Update library supports compile-time configuration to reduce code size if certain features are not needed:
### UPDATE_SIGN
Enable signature verification support (disabled by default).
Add to your project's `build_opt.h`:
```
-DUPDATE_SIGN
```
Or add to your build flags in `platformio.ini`:
```ini
build_flags = -DUPDATE_SIGN
```
**Effects:**
- Enables signature verification classes and methods
- Adds RSA and ECDSA signature verification support
- `installSignature()` method becomes available
- Increases code size due to mbedtls cryptographic functions
### UPDATE_NOCRYPT
Disable encryption/decryption support:
```cpp
#define UPDATE_NOCRYPT
#include <Update.h>
```
**Effects:**
- Removes AES encryption support
- Reduces code size
- `setupCrypt()` and related methods will not be available
**Note:** To enable signature verification while disabling encryption, add to `build_opt.h`:
```
-DUPDATE_SIGN
-DUPDATE_NOCRYPT
```
## License
This library is part of the Arduino-ESP32 project and is licensed under the Apache License 2.0.
## Contributing
Contributions are welcome! Please submit issues and pull requests on GitHub:
https://github.com/espressif/arduino-esp32
## Support
- Documentation: https://docs.espressif.com/
- Forum: https://esp32.com/
- GitHub Issues: https://github.com/espressif/arduino-esp32/issues