Skip to content

Commit e2656bb

Browse files
committed
feat(ota): Add support for signed binaries
1 parent fc8ce8f commit e2656bb

File tree

22 files changed

+2312
-4
lines changed

22 files changed

+2312
-4
lines changed

.github/workflows/build_py_tools.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ on:
88
- "tools/espota.py"
99
- "tools/gen_esp32part.py"
1010
- "tools/gen_insights_package.py"
11+
- "tools/bin_signing.py"
1112

1213
permissions:
1314
contents: write
@@ -44,6 +45,7 @@ jobs:
4445
tools/espota.py
4546
tools/gen_esp32part.py
4647
tools/gen_insights_package.py
48+
tools/bin_signing.py
4749
4850
- name: List all changed files
4951
shell: bash
@@ -108,7 +110,7 @@ jobs:
108110
- name: Install dependencies
109111
run: |
110112
python -m pip install --upgrade pip
111-
pip install pyinstaller requests
113+
pip install pyinstaller requests cryptography
112114
113115
- name: Build with PyInstaller
114116
shell: bash

CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,8 @@ set(ARDUINO_LIBRARY_Ticker_SRCS libraries/Ticker/src/Ticker.cpp)
229229

230230
set(ARDUINO_LIBRARY_Update_SRCS
231231
libraries/Update/src/Updater.cpp
232-
libraries/Update/src/HttpsOTAUpdate.cpp)
232+
libraries/Update/src/HttpsOTAUpdate.cpp
233+
libraries/Update/src/Updater_Signing.cpp)
233234

234235
set(ARDUINO_LIBRARY_USB_SRCS
235236
libraries/USB/src/USBHID.cpp
Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
# SignedOTA - Secure OTA Updates with Signature Verification
2+
3+
This example demonstrates how to perform secure OTA updates with cryptographic signature verification using the ArduinoOTA library.
4+
5+
## Overview
6+
7+
**SignedOTA** adds an extra layer of security to Arduino OTA updates by requiring all firmware to be cryptographically signed with your private key. This protects against:
8+
9+
- ✅ Unauthorized firmware updates
10+
- ✅ Man-in-the-middle attacks
11+
- ✅ Compromised networks
12+
- ✅ Firmware tampering
13+
- ✅ Supply chain attacks
14+
15+
Even if an attacker gains access to your network, they **cannot** install unsigned firmware on your devices.
16+
17+
## Features
18+
19+
- **RSA & ECDSA Support**: RSA-2048/3072/4096 and ECDSA-P256/P384
20+
- **Multiple Hash Algorithms**: SHA-256, SHA-384, SHA-512
21+
- **Arduino IDE Compatible**: Works with standard Arduino OTA workflow
22+
- **Optional Password Protection**: Add password authentication in addition to signature verification
23+
- **Easy Integration**: Just a few lines of code
24+
25+
## Requirements
26+
27+
- **ESP32 Arduino Core 3.3.0+**
28+
- **Python 3.6+** with `cryptography` library
29+
- **OTA-capable partition scheme** (e.g., "Minimal SPIFFS (1.9MB APP with OTA)")
30+
31+
## Quick Start Guide
32+
33+
### 1. Generate Cryptographic Keys
34+
35+
```bash
36+
# Navigate to Arduino ESP32 tools directory
37+
cd <ARDUINO_ROOT>/tools
38+
39+
# Install Python dependencies
40+
pip install cryptography
41+
42+
# Generate RSA-2048 key pair (recommended)
43+
python bin_signing.py --generate-key rsa-2048 --out private_key.pem
44+
45+
# Extract public key
46+
python bin_signing.py --extract-pubkey private_key.pem --out public_key.pem
47+
```
48+
49+
**⚠️ IMPORTANT: Keep `private_key.pem` secure! Anyone with this key can sign firmware for your devices.**
50+
51+
### 2. Setup the Example
52+
53+
1. Copy `public_key.h` (generated in step 1) to this sketch directory
54+
2. Open `SignedOTA.ino` in Arduino IDE
55+
3. Configure WiFi credentials:
56+
```cpp
57+
const char *ssid = "YourWiFiSSID";
58+
const char *password = "YourWiFiPassword";
59+
```
60+
4. Select appropriate partition scheme:
61+
- **Tools → Partition Scheme → "Minimal SPIFFS (1.9MB APP with OTA)"**
62+
63+
### 3. Upload Initial Firmware
64+
65+
1. Connect your ESP32 via USB
66+
2. Upload the sketch normally
67+
3. Open Serial Monitor (115200 baud)
68+
4. Note the device IP address
69+
70+
### 4. Build & Sign Firmware for OTA Update Example
71+
72+
**Option A: Using Arduino IDE**
73+
74+
```bash
75+
# Export compiled binary
76+
# In Arduino IDE: Sketch → Export Compiled Binary
77+
78+
# Sign the firmware
79+
cd <ARDUINO_ROOT>/tools
80+
python bin_signing.py \
81+
--bin /path/to/SignedOTA.ino.bin \
82+
--key private_key.pem \
83+
--out firmware_signed.bin
84+
```
85+
86+
**Option B: Using arduino-cli**
87+
88+
```bash
89+
# Compile and export
90+
arduino-cli compile --fqbn esp32:esp32:esp32 --export-binaries SignedOTA
91+
92+
# Sign the firmware
93+
cd <ARDUINO_ROOT>/tools
94+
python bin_signing.py \
95+
--bin build/esp32.esp32.esp32/SignedOTA.ino.bin \
96+
--key private_key.pem \
97+
--out firmware_signed.bin
98+
```
99+
100+
### 5. Upload Signed Firmware via OTA
101+
102+
Upload the signed firmware using `espota.py`:
103+
104+
```bash
105+
python <ARDUINO_ROOT>/tools/espota.py -i <device-ip> -f firmware_signed.bin
106+
```
107+
108+
The device will automatically:
109+
1. Receive the signed firmware (firmware + signature)
110+
2. Hash only the firmware portion
111+
3. Verify the signature
112+
4. Install if valid, reject if invalid
113+
114+
**Note**: You can also use the Update library's `Signed_OTA_Update` example for HTTP-based OTA updates.
115+
116+
## Configuration Options
117+
118+
### Hash Algorithms
119+
120+
Choose one in `SignedOTA.ino`:
121+
122+
```cpp
123+
#define USE_SHA256 // Default, fastest
124+
// #define USE_SHA384
125+
// #define USE_SHA512
126+
```
127+
128+
**Must match** the `--hash` parameter when signing:
129+
130+
```bash
131+
python bin_signing.py --bin firmware.bin --key private.pem --out signed.bin --hash sha256
132+
```
133+
134+
### Signature Algorithms
135+
136+
Choose one in `SignedOTA.ino`:
137+
138+
```cpp
139+
#define USE_RSA // For RSA keys
140+
// #define USE_ECDSA // For ECDSA keys
141+
```
142+
143+
### Optional Password Protection
144+
145+
Add password authentication **in addition to** signature verification:
146+
147+
```cpp
148+
const char *ota_password = "yourpassword"; // Set password
149+
// const char *ota_password = nullptr; // Disable password
150+
```
151+
152+
## How It Works
153+
154+
```
155+
┌─────────────────┐
156+
│ Build Firmware │
157+
└────────┬────────┘
158+
159+
160+
┌─────────────────┐
161+
│ Sign Firmware │ ← Uses your private key
162+
│ (bin_signing) │
163+
└────────┬────────┘
164+
165+
166+
┌─────────────────────────┐
167+
│ firmware_signed.bin │
168+
│ [firmware][signature] │
169+
└────────┬────────────────┘
170+
171+
▼ OTA Upload
172+
┌─────────────────────────┐
173+
│ ESP32 Device │
174+
│ ┌──────────────────┐ │
175+
│ │ Verify Signature │ │ ← Uses your public key
176+
│ │ ✓ or ✗ │ │
177+
│ └──────────────────┘ │
178+
│ │ │
179+
│ ✓ Valid? │
180+
│ ├─ Yes: Install │
181+
│ └─ No: Reject │
182+
└─────────────────────────┘
183+
```
184+
185+
## Troubleshooting
186+
187+
### "Begin Failed" Error
188+
189+
**Cause**: Signature verification setup failed, or partition scheme issue
190+
191+
**Solutions**:
192+
1. Check partition scheme (use "Minimal SPIFFS (1.9MB APP with OTA)")
193+
2. Verify `public_key.h` is in the sketch directory
194+
3. Check hash and signature algorithm match your key type
195+
196+
### "End Failed" Error
197+
198+
**Cause**: Signature verification failed
199+
200+
**Solutions**:
201+
1. Ensure firmware was signed with the **correct private key**
202+
2. Verify hash algorithm matches (SHA-256, SHA-384, SHA-512)
203+
3. Check firmware wasn't corrupted during signing/transfer
204+
4. Confirm you signed the **correct** `.bin` file
205+
206+
### "Receive Failed" Error
207+
208+
**Cause**: Network timeout or connection issue
209+
210+
**Solutions**:
211+
1. Check WiFi signal strength
212+
2. Ensure device is reachable on the network
213+
3. Try increasing timeout: `ArduinoOTA.setTimeout(5000)`
214+
215+
### Upload Fails
216+
217+
**Issue**: OTA upload fails or times out
218+
219+
**Solutions**:
220+
1. Verify device is on the same network
221+
2. Check firewall settings aren't blocking port 3232
222+
3. Ensure WiFi signal strength is adequate
223+
4. If using password protection, ensure the password is correct
224+
5. Try: `python <ARDUINO_ROOT>/tools/espota.py -i <device-ip> -f firmware_signed.bin -d`
225+
226+
## Security Considerations
227+
228+
### Best Practices
229+
230+
**Keep private key secure**: Never commit to git, store encrypted
231+
**Use strong keys**: RSA-2048+ or ECDSA-P256+
232+
**Use HTTPS when possible**: For additional transport security
233+
**Add password authentication**: Extra layer of protection
234+
**Rotate keys periodically**: Generate new keys every 1-2 years
235+
236+
### What This Protects Against
237+
238+
- ✅ Unsigned firmware installation
239+
- ✅ Firmware signed with wrong key
240+
- ✅ Tampered/corrupted firmware
241+
- ✅ Network-based attacks (when combined with password)
242+
243+
### What This Does NOT Protect Against
244+
245+
- ❌ Physical access (USB flashing still works)
246+
- ❌ Downgrade attacks (no version checking by default)
247+
- ❌ Replay attacks (no timestamp/nonce by default)
248+
- ❌ Key compromise (if private key is stolen)
249+
250+
### Additional Security
251+
252+
For production deployments, consider:
253+
254+
1. **Add version checking** to prevent downgrades
255+
2. **Add timestamp validation** to prevent replay attacks
256+
3. **Use secure boot** for additional protection
257+
4. **Store keys in HSM** or secure key management system
258+
5. **Implement key rotation** mechanism
259+
260+
## Advanced Usage
261+
262+
### Using ECDSA Instead of RSA
263+
264+
ECDSA keys are smaller and faster:
265+
266+
```bash
267+
# Generate ECDSA-P256 key
268+
python bin_signing.py --generate-key ecdsa-p256 --out private_key.pem
269+
python bin_signing.py --extract-pubkey private_key.pem --out public_key.pem
270+
```
271+
272+
In `SignedOTA.ino`:
273+
274+
```cpp
275+
#define USE_SHA256
276+
#define USE_ECDSA // Instead of USE_RSA
277+
```
278+
279+
### Using SHA-384 or SHA-512
280+
281+
For higher security:
282+
283+
```bash
284+
# Sign with SHA-384
285+
python bin_signing.py --bin firmware.bin --key private.pem --out signed.bin --hash sha384
286+
```
287+
288+
In `SignedOTA.ino`:
289+
290+
```cpp
291+
#define USE_SHA384 // Instead of USE_SHA256
292+
#define USE_RSA
293+
```
294+
295+
### Custom Partition Label
296+
297+
To update a specific partition:
298+
299+
```cpp
300+
ArduinoOTA.setPartitionLabel("my_partition");
301+
```
302+
303+
## Support
304+
305+
For issues and questions:
306+
307+
- Update Library README: `libraries/Update/README.md`
308+
- ESP32 Arduino Core: https://github.com/espressif/arduino-esp32
309+
- Forum: https://github.com/espressif/arduino-esp32/discussions
310+
311+
## License
312+
313+
This library is part of the Arduino-ESP32 project and is licensed under the Apache License 2.0.
314+

0 commit comments

Comments
 (0)