|  | 
|  | 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