Skip to content

Commit 9049868

Browse files
committed
[embassy-usb-dfu] support ed25519 verification
This commit adds the ability to verify that USB DFU updates are correctly signed using ed25519. This required adding support to embassy-boot for reading from the DFU partition.
1 parent edceb0d commit 9049868

File tree

13 files changed

+114
-11
lines changed

13 files changed

+114
-11
lines changed

ci.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ cargo batch \
282282
--- build --release --manifest-path examples/boot/bootloader/rp/Cargo.toml --target thumbv6m-none-eabi \
283283
--- build --release --manifest-path examples/boot/bootloader/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32l496zg \
284284
--- build --release --manifest-path examples/boot/bootloader/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wb55rg \
285+
--- build --release --manifest-path examples/boot/bootloader/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wb55rg,verify \
285286
--- build --release --manifest-path examples/boot/bootloader/stm32-dual-bank/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32h743zi \
286287
--- build --release --manifest-path examples/wasm/Cargo.toml --target wasm32-unknown-unknown --artifact-dir out/examples/wasm \
287288
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103c8 --artifact-dir out/tests/stm32f103c8 \

embassy-boot/src/firmware_updater/asynch.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,17 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
161161
Ok(())
162162
}
163163

164+
/// Read a slice of data from the DFU storage peripheral, starting the read
165+
/// operation at the given address offset, and reading `buf.len()` bytes.
166+
///
167+
/// # Errors
168+
///
169+
/// Returns an error if the arguments are not aligned or out of bounds.
170+
pub async fn read_dfu(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
171+
self.dfu.read(offset, buf).await?;
172+
Ok(())
173+
}
174+
164175
/// Mark to trigger firmware swap on next boot.
165176
#[cfg(not(feature = "_verify"))]
166177
pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> {

embassy-boot/src/firmware_updater/blocking.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,17 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
196196
Ok(())
197197
}
198198

199+
/// Read a slice of data from the DFU storage peripheral, starting the read
200+
/// operation at the given address offset, and reading `buf.len()` bytes.
201+
///
202+
/// # Errors
203+
///
204+
/// Returns an error if the arguments are not aligned or out of bounds.
205+
pub fn read_dfu(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
206+
self.dfu.read(offset, buf)?;
207+
Ok(())
208+
}
209+
199210
/// Mark to trigger firmware swap on next boot.
200211
#[cfg(not(feature = "_verify"))]
201212
pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> {

embassy-usb-dfu/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,8 @@ esp32c3-hal = { version = "0.13.0", optional = true, default-features = false }
4343
dfu = []
4444
application = []
4545
defmt = ["dep:defmt", "embassy-boot/defmt", "embassy-usb/defmt"]
46+
ed25519-dalek = ["embassy-boot/ed25519-dalek", "_verify"]
47+
ed25519-salty = ["embassy-boot/ed25519-salty", "_verify"]
48+
49+
# Internal features
50+
_verify = []

embassy-usb-dfu/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,9 @@ An implementation of the USB DFU 1.1 protocol using embassy-boot. It has 2 compo
44

55
* DFU protocol mode, enabled by the `dfu` feature. This mode corresponds to the transfer phase DFU protocol described by the USB IF. It supports DFU_DNLOAD requests if marked by the user, and will automatically reset the chip once a DFU transaction has been completed. It also responds to DFU_GETSTATUS, DFU_GETSTATE, DFU_ABORT, and DFU_CLRSTATUS with no user intervention.
66
* DFU runtime mode, enabled by the `application feature`. This mode allows users to expose a DFU interface on their USB device, informing the host of the capability to DFU over USB, and allowing the host to reset the device into its bootloader to complete a DFU operation. Supports DFU_GETSTATUS and DFU_DETACH. When detach/reset is seen by the device as described by the standard, will write a new DFU magic number into the bootloader state in flash, and reset the system.
7+
8+
## Verification
9+
10+
Embassy-boot provides functionality to verify that an update binary has been correctly signed using ed25519 as described in https://embassy.dev/book/#_verification. Even though the linked procedure describes the signature being concatenated to the end of the update binary, embassy-boot does not force this and is flexible in terms of how the signature for a binary is distributed. The current implementation in embassy-usb-dfu does however assume that the signature is 64 bytes long and concatenated to the end of the update binary since this is the simplest way to make it work with the usb-dfu mechanism. I.e. embassy-usb-dfu does not currently offer the same flexibility as embassy-boot.
11+
12+
To enable verification, you need to enable either the `ed25519-dalek` or the `ed25519-salty` feature with `ed25519-salty` being recommended.

embassy-usb-dfu/src/dfu.rs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,19 @@ pub struct Control<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_S
1919
offset: usize,
2020
buf: AlignedBuffer<BLOCK_SIZE>,
2121
reset: RST,
22+
23+
#[cfg(feature = "_verify")]
24+
public_key: &'static [u8; 32],
2225
}
2326

2427
impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Control<'d, DFU, STATE, RST, BLOCK_SIZE> {
2528
/// Create a new DFU instance to handle DFU transfers.
26-
pub fn new(updater: BlockingFirmwareUpdater<'d, DFU, STATE>, attrs: DfuAttributes, reset: RST) -> Self {
29+
pub fn new(
30+
updater: BlockingFirmwareUpdater<'d, DFU, STATE>,
31+
attrs: DfuAttributes,
32+
reset: RST,
33+
#[cfg(feature = "_verify")] public_key: &'static [u8; 32],
34+
) -> Self {
2735
Self {
2836
updater,
2937
attrs,
@@ -32,6 +40,9 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Co
3240
offset: 0,
3341
buf: AlignedBuffer([0; BLOCK_SIZE]),
3442
reset,
43+
44+
#[cfg(feature = "_verify")]
45+
public_key,
3546
}
3647
}
3748

@@ -102,7 +113,23 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Ha
102113
if final_transfer {
103114
debug!("Receiving final transfer");
104115

105-
match self.updater.mark_updated() {
116+
#[cfg(feature = "_verify")]
117+
let update_res: Result<(), FirmwareUpdaterError> = {
118+
const SIGNATURE_LEN: usize = 64;
119+
120+
let mut signature = [0; SIGNATURE_LEN];
121+
let update_len = (self.offset - SIGNATURE_LEN) as u32;
122+
123+
self.updater.read_dfu(update_len, &mut signature).and_then(|_| {
124+
self.updater
125+
.verify_and_mark_updated(self.public_key, &signature, update_len)
126+
})
127+
};
128+
129+
#[cfg(not(feature = "_verify"))]
130+
let update_res = self.updater.mark_updated();
131+
132+
match update_res {
106133
Ok(_) => {
107134
self.status = Status::Ok;
108135
self.state = State::ManifestSync;
@@ -168,7 +195,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Ha
168195
Some(InResponse::Accepted(&buf[0..1]))
169196
}
170197
Ok(Request::Upload) if self.attrs.contains(DfuAttributes::CAN_UPLOAD) => {
171-
//TODO: FirmwareUpdater does not provide a way of reading the active partition, can't upload.
198+
//TODO: FirmwareUpdater provides a way of reading the active partition so we could in theory add functionality to upload firmware.
172199
Some(InResponse::Rejected)
173200
}
174201
_ => None,

examples/boot/application/stm32wb-dfu/memory.x

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
MEMORY
22
{
33
/* NOTE 1 K = 1 KiBi = 1024 bytes */
4-
BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 24K
5-
BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K
6-
FLASH : ORIGIN = 0x08008000, LENGTH = 128K
7-
DFU : ORIGIN = 0x08028000, LENGTH = 132K
4+
BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 48K
5+
BOOTLOADER_STATE : ORIGIN = 0x0800C000, LENGTH = 4K
6+
FLASH : ORIGIN = 0x0800D000, LENGTH = 120K
7+
DFU : ORIGIN = 0x0802B000, LENGTH = 120K
88
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K
99
}
1010

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
untrusted comment: signify secret key
2+
RWRCSwAAAAATdHQF3B4jEIoNZrjADRp2LbjJjNdNNzKwTCe4IB6mDNq96pe53nbNxwbdCc/T4hrz7W+Kx1MwrZ0Yz5xebSK5Z0Kh/3Cdf039U5f+eoTDS2fIGbohyUbrtwKzjyE0qXI=

examples/boot/bootloader/stm32wb-dfu/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ defmt = [
3030
"embassy-usb/defmt",
3131
"embassy-usb-dfu/defmt"
3232
]
33+
verify = ["embassy-usb-dfu/ed25519-salty"]
3334

3435
[profile.dev]
3536
debug = 2

examples/boot/bootloader/stm32wb-dfu/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,32 @@ cargo objcopy --release -- -O binary fw.bin
2828
dfu-util -d c0de:cafe -w -D fw.bin
2929
```
3030

31+
### 3. Sign Updates Before Flashing (Optional)
32+
33+
Currently, embassy-usb-dfu only supports a limited implementation of the generic support for ed25519-based update verfication in embassy-boot. This implementation assumes that a signature is simply concatenated to the end of an update binary. For more details, please see https://embassy.dev/book/#_verification and/or refer to the documentation for embassy-boot-dfu.
34+
35+
To sign (and then verify) application updates, you will first need to generate a key pair:
36+
37+
```
38+
signify-openbsd -G -n -p secrets/key.pub -s secrets/key.sec
39+
tail -n1 secrets/key.pub | base64 -d -i - | dd ibs=10 skip=1 > secrets/key.pub.short
40+
```
41+
42+
Then you will need to sign all you binaries with the private key:
43+
44+
```
45+
cargo objcopy --release -- -O binary fw.bin
46+
shasum -a 512 -b fw.bin | head -c128 | xxd -p -r > target/fw-hash.txt
47+
signify-openbsd -S -s secrets/key.sec -m target/fw-hash.txt -x target/fw-hash.sig
48+
cp fw.bin fw-signed.bin
49+
tail -n1 target/fw-hash.sig | base64 -d -i - | dd ibs=10 skip=1 >> fw-signed.bin
50+
dfu-util -d c0de:cafe -w -D fw-signed.bin
51+
```
52+
53+
Finally, as shown in this example with the `verify` feature flag enabled, you then need to embed the public key into your bootloader so that it can verify update signatures.
54+
55+
N.B. Please note that the exact steps above are NOT a good example of how to manage your keys securely. In a production environment, you should take great care to ensure that (at least the private key) is protected and not leaked into your version control system.
56+
3157
## Troubleshooting
3258

3359
- Make sure your device is in DFU mode before flashing

examples/boot/bootloader/stm32wb-dfu/memory.x

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
MEMORY
22
{
33
/* NOTE 1 K = 1 KiBi = 1024 bytes */
4-
FLASH : ORIGIN = 0x08000000, LENGTH = 24K
5-
BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K
6-
ACTIVE : ORIGIN = 0x08008000, LENGTH = 128K
7-
DFU : ORIGIN = 0x08028000, LENGTH = 132K
4+
FLASH : ORIGIN = 0x08000000, LENGTH = 48K
5+
BOOTLOADER_STATE : ORIGIN = 0x0800C000, LENGTH = 4K
6+
ACTIVE : ORIGIN = 0x0800D000, LENGTH = 120K
7+
DFU : ORIGIN = 0x0802B000, LENGTH = 120K
88
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 16K
99
}
1010

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
gB��p�M�S��z��Kg��!�F���!4�r

examples/boot/bootloader/stm32wb-dfu/src/main.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ bind_interrupts!(struct Irqs {
2525
// N.B. update to a custom GUID for your own device!
2626
const DEVICE_INTERFACE_GUIDS: &[&str] = &["{EAA9A5DC-30BA-44BC-9232-606CDC875321}"];
2727

28+
// This is a randomly generated example key.
29+
//
30+
// N.B. Please replace with your own!
31+
#[cfg(feature = "verify")]
32+
static PUBLIC_SIGNING_KEY: &[u8; 32] = include_bytes!("../secrets/key.pub.short");
33+
2834
#[entry]
2935
fn main() -> ! {
3036
let mut config = embassy_stm32::Config::default();
@@ -57,7 +63,13 @@ fn main() -> ! {
5763
let mut config_descriptor = [0; 256];
5864
let mut bos_descriptor = [0; 256];
5965
let mut control_buf = [0; 4096];
66+
67+
#[cfg(not(feature = "verify"))]
6068
let mut state = Control::new(updater, DfuAttributes::CAN_DOWNLOAD, ResetImmediate);
69+
70+
#[cfg(feature = "verify")]
71+
let mut state = Control::new(updater, DfuAttributes::CAN_DOWNLOAD, ResetImmediate, PUBLIC_SIGNING_KEY);
72+
6173
let mut builder = Builder::new(
6274
driver,
6375
config,

0 commit comments

Comments
 (0)