432 lines
11 KiB
Markdown
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
|