aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--embassy-boot/boot/src/firmware_updater.rs543
-rw-r--r--embassy-boot/boot/src/firmware_updater/asynch.rs251
-rw-r--r--embassy-boot/boot/src/firmware_updater/blocking.rs221
-rw-r--r--embassy-boot/boot/src/firmware_updater/mod.rs71
4 files changed, 543 insertions, 543 deletions
diff --git a/embassy-boot/boot/src/firmware_updater.rs b/embassy-boot/boot/src/firmware_updater.rs
deleted file mode 100644
index aeea206f9..000000000
--- a/embassy-boot/boot/src/firmware_updater.rs
+++ /dev/null
@@ -1,543 +0,0 @@
1use digest::Digest;
2use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind};
3#[cfg(feature = "nightly")]
4use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash;
5
6use crate::{Partition, State, BOOT_MAGIC, SWAP_MAGIC};
7
8/// Errors returned by FirmwareUpdater
9#[derive(Debug)]
10pub enum FirmwareUpdaterError {
11 /// Error from flash.
12 Flash(NorFlashErrorKind),
13 /// Signature errors.
14 Signature(signature::Error),
15}
16
17#[cfg(feature = "defmt")]
18impl defmt::Format for FirmwareUpdaterError {
19 fn format(&self, fmt: defmt::Formatter) {
20 match self {
21 FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"),
22 FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"),
23 }
24 }
25}
26
27impl<E> From<E> for FirmwareUpdaterError
28where
29 E: NorFlashError,
30{
31 fn from(error: E) -> Self {
32 FirmwareUpdaterError::Flash(error.kind())
33 }
34}
35
36/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
37/// 'mess up' the internal bootloader state
38pub struct FirmwareUpdater {
39 state: Partition,
40 dfu: Partition,
41}
42
43#[cfg(target_os = "none")]
44impl Default for FirmwareUpdater {
45 fn default() -> Self {
46 extern "C" {
47 static __bootloader_state_start: u32;
48 static __bootloader_state_end: u32;
49 static __bootloader_dfu_start: u32;
50 static __bootloader_dfu_end: u32;
51 }
52
53 let dfu = unsafe {
54 Partition::new(
55 &__bootloader_dfu_start as *const u32 as u32,
56 &__bootloader_dfu_end as *const u32 as u32,
57 )
58 };
59 let state = unsafe {
60 Partition::new(
61 &__bootloader_state_start as *const u32 as u32,
62 &__bootloader_state_end as *const u32 as u32,
63 )
64 };
65
66 trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
67 trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
68 FirmwareUpdater::new(dfu, state)
69 }
70}
71
72impl FirmwareUpdater {
73 /// Create a firmware updater instance with partition ranges for the update and state partitions.
74 pub const fn new(dfu: Partition, state: Partition) -> Self {
75 Self { dfu, state }
76 }
77
78 /// Obtain the current state.
79 ///
80 /// This is useful to check if the bootloader has just done a swap, in order
81 /// to do verifications and self-tests of the new image before calling
82 /// `mark_booted`.
83 #[cfg(feature = "nightly")]
84 pub async fn get_state<F: AsyncNorFlash>(
85 &mut self,
86 state_flash: &mut F,
87 aligned: &mut [u8],
88 ) -> Result<State, FirmwareUpdaterError> {
89 self.state.read(state_flash, 0, aligned).await?;
90
91 if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
92 Ok(State::Swap)
93 } else {
94 Ok(State::Boot)
95 }
96 }
97
98 /// Verify the DFU given a public key. If there is an error then DO NOT
99 /// proceed with updating the firmware as it must be signed with a
100 /// corresponding private key (otherwise it could be malicious firmware).
101 ///
102 /// Mark to trigger firmware swap on next boot if verify suceeds.
103 ///
104 /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
105 /// been generated from a SHA-512 digest of the firmware bytes.
106 ///
107 /// If no signature feature is set then this method will always return a
108 /// signature error.
109 ///
110 /// # Safety
111 ///
112 /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from
113 /// and written to.
114 #[cfg(all(feature = "_verify", feature = "nightly"))]
115 pub async fn verify_and_mark_updated<F: AsyncNorFlash>(
116 &mut self,
117 _state_and_dfu_flash: &mut F,
118 _public_key: &[u8],
119 _signature: &[u8],
120 _update_len: u32,
121 _aligned: &mut [u8],
122 ) -> Result<(), FirmwareUpdaterError> {
123 assert_eq!(_aligned.len(), F::WRITE_SIZE);
124 assert!(_update_len <= self.dfu.size());
125
126 #[cfg(feature = "ed25519-dalek")]
127 {
128 use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier};
129
130 use crate::digest_adapters::ed25519_dalek::Sha512;
131
132 let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
133
134 let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
135 let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
136
137 let mut message = [0; 64];
138 self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)
139 .await?;
140
141 public_key.verify(&message, &signature).map_err(into_signature_error)?
142 }
143 #[cfg(feature = "ed25519-salty")]
144 {
145 use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
146 use salty::{PublicKey, Signature};
147
148 use crate::digest_adapters::salty::Sha512;
149
150 fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
151 FirmwareUpdaterError::Signature(signature::Error::default())
152 }
153
154 let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
155 let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
156 let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
157 let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
158
159 let mut message = [0; 64];
160 self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)
161 .await?;
162
163 let r = public_key.verify(&message, &signature);
164 trace!(
165 "Verifying with public key {}, signature {} and message {} yields ok: {}",
166 public_key.to_bytes(),
167 signature.to_bytes(),
168 message,
169 r.is_ok()
170 );
171 r.map_err(into_signature_error)?
172 }
173
174 self.set_magic(_aligned, SWAP_MAGIC, _state_and_dfu_flash).await
175 }
176
177 /// Verify the update in DFU with any digest.
178 #[cfg(feature = "nightly")]
179 pub async fn hash<F: AsyncNorFlash, D: Digest>(
180 &mut self,
181 dfu_flash: &mut F,
182 update_len: u32,
183 chunk_buf: &mut [u8],
184 output: &mut [u8],
185 ) -> Result<(), FirmwareUpdaterError> {
186 let mut digest = D::new();
187 for offset in (0..update_len).step_by(chunk_buf.len()) {
188 self.dfu.read(dfu_flash, offset, chunk_buf).await?;
189 let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len());
190 digest.update(&chunk_buf[..len]);
191 }
192 output.copy_from_slice(digest.finalize().as_slice());
193 Ok(())
194 }
195
196 /// Mark to trigger firmware swap on next boot.
197 ///
198 /// # Safety
199 ///
200 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
201 #[cfg(all(feature = "nightly", not(feature = "_verify")))]
202 pub async fn mark_updated<F: AsyncNorFlash>(
203 &mut self,
204 state_flash: &mut F,
205 aligned: &mut [u8],
206 ) -> Result<(), FirmwareUpdaterError> {
207 assert_eq!(aligned.len(), F::WRITE_SIZE);
208 self.set_magic(aligned, SWAP_MAGIC, state_flash).await
209 }
210
211 /// Mark firmware boot successful and stop rollback on reset.
212 ///
213 /// # Safety
214 ///
215 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
216 #[cfg(feature = "nightly")]
217 pub async fn mark_booted<F: AsyncNorFlash>(
218 &mut self,
219 state_flash: &mut F,
220 aligned: &mut [u8],
221 ) -> Result<(), FirmwareUpdaterError> {
222 assert_eq!(aligned.len(), F::WRITE_SIZE);
223 self.set_magic(aligned, BOOT_MAGIC, state_flash).await
224 }
225
226 #[cfg(feature = "nightly")]
227 async fn set_magic<F: AsyncNorFlash>(
228 &mut self,
229 aligned: &mut [u8],
230 magic: u8,
231 state_flash: &mut F,
232 ) -> Result<(), FirmwareUpdaterError> {
233 self.state.read(state_flash, 0, aligned).await?;
234
235 if aligned.iter().any(|&b| b != magic) {
236 // Read progress validity
237 self.state.read(state_flash, F::WRITE_SIZE as u32, aligned).await?;
238
239 // FIXME: Do not make this assumption.
240 const STATE_ERASE_VALUE: u8 = 0xFF;
241
242 if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
243 // The current progress validity marker is invalid
244 } else {
245 // Invalidate progress
246 aligned.fill(!STATE_ERASE_VALUE);
247 self.state.write(state_flash, F::WRITE_SIZE as u32, aligned).await?;
248 }
249
250 // Clear magic and progress
251 self.state.wipe(state_flash).await?;
252
253 // Set magic
254 aligned.fill(magic);
255 self.state.write(state_flash, 0, aligned).await?;
256 }
257 Ok(())
258 }
259
260 /// Write data to a flash page.
261 ///
262 /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
263 ///
264 /// # Safety
265 ///
266 /// Failing to meet alignment and size requirements may result in a panic.
267 #[cfg(feature = "nightly")]
268 pub async fn write_firmware<F: AsyncNorFlash>(
269 &mut self,
270 offset: usize,
271 data: &[u8],
272 dfu_flash: &mut F,
273 ) -> Result<(), FirmwareUpdaterError> {
274 assert!(data.len() >= F::ERASE_SIZE);
275
276 self.dfu
277 .erase(dfu_flash, offset as u32, (offset + data.len()) as u32)
278 .await?;
279
280 self.dfu.write(dfu_flash, offset as u32, data).await?;
281
282 Ok(())
283 }
284
285 /// Prepare for an incoming DFU update by erasing the entire DFU area and
286 /// returning its `Partition`.
287 ///
288 /// Using this instead of `write_firmware` allows for an optimized API in
289 /// exchange for added complexity.
290 #[cfg(feature = "nightly")]
291 pub async fn prepare_update<F: AsyncNorFlash>(
292 &mut self,
293 dfu_flash: &mut F,
294 ) -> Result<Partition, FirmwareUpdaterError> {
295 self.dfu.wipe(dfu_flash).await?;
296
297 Ok(self.dfu)
298 }
299
300 //
301 // Blocking API
302 //
303
304 /// Obtain the current state.
305 ///
306 /// This is useful to check if the bootloader has just done a swap, in order
307 /// to do verifications and self-tests of the new image before calling
308 /// `mark_booted`.
309 pub fn get_state_blocking<F: NorFlash>(
310 &mut self,
311 state_flash: &mut F,
312 aligned: &mut [u8],
313 ) -> Result<State, FirmwareUpdaterError> {
314 self.state.read_blocking(state_flash, 0, aligned)?;
315
316 if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
317 Ok(State::Swap)
318 } else {
319 Ok(State::Boot)
320 }
321 }
322
323 /// Verify the DFU given a public key. If there is an error then DO NOT
324 /// proceed with updating the firmware as it must be signed with a
325 /// corresponding private key (otherwise it could be malicious firmware).
326 ///
327 /// Mark to trigger firmware swap on next boot if verify suceeds.
328 ///
329 /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
330 /// been generated from a SHA-512 digest of the firmware bytes.
331 ///
332 /// If no signature feature is set then this method will always return a
333 /// signature error.
334 ///
335 /// # Safety
336 ///
337 /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from
338 /// and written to.
339 #[cfg(feature = "_verify")]
340 pub fn verify_and_mark_updated_blocking<F: NorFlash>(
341 &mut self,
342 _state_and_dfu_flash: &mut F,
343 _public_key: &[u8],
344 _signature: &[u8],
345 _update_len: u32,
346 _aligned: &mut [u8],
347 ) -> Result<(), FirmwareUpdaterError> {
348 assert_eq!(_aligned.len(), F::WRITE_SIZE);
349 assert!(_update_len <= self.dfu.size());
350
351 #[cfg(feature = "ed25519-dalek")]
352 {
353 use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier};
354
355 use crate::digest_adapters::ed25519_dalek::Sha512;
356
357 let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
358
359 let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
360 let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
361
362 let mut message = [0; 64];
363 self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?;
364
365 public_key.verify(&message, &signature).map_err(into_signature_error)?
366 }
367 #[cfg(feature = "ed25519-salty")]
368 {
369 use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
370 use salty::{PublicKey, Signature};
371
372 use crate::digest_adapters::salty::Sha512;
373
374 fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
375 FirmwareUpdaterError::Signature(signature::Error::default())
376 }
377
378 let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
379 let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
380 let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
381 let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
382
383 let mut message = [0; 64];
384 self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?;
385
386 let r = public_key.verify(&message, &signature);
387 trace!(
388 "Verifying with public key {}, signature {} and message {} yields ok: {}",
389 public_key.to_bytes(),
390 signature.to_bytes(),
391 message,
392 r.is_ok()
393 );
394 r.map_err(into_signature_error)?
395 }
396
397 self.set_magic_blocking(_aligned, SWAP_MAGIC, _state_and_dfu_flash)
398 }
399
400 /// Verify the update in DFU with any digest.
401 pub fn hash_blocking<F: NorFlash, D: Digest>(
402 &mut self,
403 dfu_flash: &mut F,
404 update_len: u32,
405 chunk_buf: &mut [u8],
406 output: &mut [u8],
407 ) -> Result<(), FirmwareUpdaterError> {
408 let mut digest = D::new();
409 for offset in (0..update_len).step_by(chunk_buf.len()) {
410 self.dfu.read_blocking(dfu_flash, offset, chunk_buf)?;
411 let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len());
412 digest.update(&chunk_buf[..len]);
413 }
414 output.copy_from_slice(digest.finalize().as_slice());
415 Ok(())
416 }
417
418 /// Mark to trigger firmware swap on next boot.
419 ///
420 /// # Safety
421 ///
422 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
423 #[cfg(not(feature = "_verify"))]
424 pub fn mark_updated_blocking<F: NorFlash>(
425 &mut self,
426 state_flash: &mut F,
427 aligned: &mut [u8],
428 ) -> Result<(), FirmwareUpdaterError> {
429 assert_eq!(aligned.len(), F::WRITE_SIZE);
430 self.set_magic_blocking(aligned, SWAP_MAGIC, state_flash)
431 }
432
433 /// Mark firmware boot successful and stop rollback on reset.
434 ///
435 /// # Safety
436 ///
437 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
438 pub fn mark_booted_blocking<F: NorFlash>(
439 &mut self,
440 state_flash: &mut F,
441 aligned: &mut [u8],
442 ) -> Result<(), FirmwareUpdaterError> {
443 assert_eq!(aligned.len(), F::WRITE_SIZE);
444 self.set_magic_blocking(aligned, BOOT_MAGIC, state_flash)
445 }
446
447 fn set_magic_blocking<F: NorFlash>(
448 &mut self,
449 aligned: &mut [u8],
450 magic: u8,
451 state_flash: &mut F,
452 ) -> Result<(), FirmwareUpdaterError> {
453 self.state.read_blocking(state_flash, 0, aligned)?;
454
455 if aligned.iter().any(|&b| b != magic) {
456 // Read progress validity
457 self.state.read_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?;
458
459 // FIXME: Do not make this assumption.
460 const STATE_ERASE_VALUE: u8 = 0xFF;
461
462 if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
463 // The current progress validity marker is invalid
464 } else {
465 // Invalidate progress
466 aligned.fill(!STATE_ERASE_VALUE);
467 self.state.write_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?;
468 }
469
470 // Clear magic and progress
471 self.state.wipe_blocking(state_flash)?;
472
473 // Set magic
474 aligned.fill(magic);
475 self.state.write_blocking(state_flash, 0, aligned)?;
476 }
477 Ok(())
478 }
479
480 /// Write data to a flash page.
481 ///
482 /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
483 ///
484 /// # Safety
485 ///
486 /// Failing to meet alignment and size requirements may result in a panic.
487 pub fn write_firmware_blocking<F: NorFlash>(
488 &mut self,
489 offset: usize,
490 data: &[u8],
491 dfu_flash: &mut F,
492 ) -> Result<(), FirmwareUpdaterError> {
493 assert!(data.len() >= F::ERASE_SIZE);
494
495 self.dfu
496 .erase_blocking(dfu_flash, offset as u32, (offset + data.len()) as u32)?;
497
498 self.dfu.write_blocking(dfu_flash, offset as u32, data)?;
499
500 Ok(())
501 }
502
503 /// Prepare for an incoming DFU update by erasing the entire DFU area and
504 /// returning its `Partition`.
505 ///
506 /// Using this instead of `write_firmware_blocking` allows for an optimized
507 /// API in exchange for added complexity.
508 pub fn prepare_update_blocking<F: NorFlash>(&mut self, flash: &mut F) -> Result<Partition, FirmwareUpdaterError> {
509 self.dfu.wipe_blocking(flash)?;
510
511 Ok(self.dfu)
512 }
513}
514
515#[cfg(test)]
516mod tests {
517 use futures::executor::block_on;
518 use sha1::{Digest, Sha1};
519
520 use super::*;
521 use crate::mem_flash::MemFlash;
522
523 #[test]
524 #[cfg(feature = "nightly")]
525 fn can_verify_sha1() {
526 const STATE: Partition = Partition::new(0, 4096);
527 const DFU: Partition = Partition::new(65536, 131072);
528
529 let mut flash = MemFlash::<131072, 4096, 8>::default();
530
531 let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
532 let mut to_write = [0; 4096];
533 to_write[..7].copy_from_slice(update.as_slice());
534
535 let mut updater = FirmwareUpdater::new(DFU, STATE);
536 block_on(updater.write_firmware(0, to_write.as_slice(), &mut flash)).unwrap();
537 let mut chunk_buf = [0; 2];
538 let mut hash = [0; 20];
539 block_on(updater.hash::<_, Sha1>(&mut flash, update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
540
541 assert_eq!(Sha1::digest(update).as_slice(), hash);
542 }
543}
diff --git a/embassy-boot/boot/src/firmware_updater/asynch.rs b/embassy-boot/boot/src/firmware_updater/asynch.rs
new file mode 100644
index 000000000..bdd03bff4
--- /dev/null
+++ b/embassy-boot/boot/src/firmware_updater/asynch.rs
@@ -0,0 +1,251 @@
1use digest::Digest;
2use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash;
3
4use crate::{FirmwareUpdater, FirmwareUpdaterError, Partition, State, BOOT_MAGIC, SWAP_MAGIC};
5
6impl FirmwareUpdater {
7 /// Obtain the current state.
8 ///
9 /// This is useful to check if the bootloader has just done a swap, in order
10 /// to do verifications and self-tests of the new image before calling
11 /// `mark_booted`.
12 pub async fn get_state<F: AsyncNorFlash>(
13 &mut self,
14 state_flash: &mut F,
15 aligned: &mut [u8],
16 ) -> Result<State, FirmwareUpdaterError> {
17 self.state.read(state_flash, 0, aligned).await?;
18
19 if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
20 Ok(State::Swap)
21 } else {
22 Ok(State::Boot)
23 }
24 }
25
26 /// Verify the DFU given a public key. If there is an error then DO NOT
27 /// proceed with updating the firmware as it must be signed with a
28 /// corresponding private key (otherwise it could be malicious firmware).
29 ///
30 /// Mark to trigger firmware swap on next boot if verify suceeds.
31 ///
32 /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
33 /// been generated from a SHA-512 digest of the firmware bytes.
34 ///
35 /// If no signature feature is set then this method will always return a
36 /// signature error.
37 ///
38 /// # Safety
39 ///
40 /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from
41 /// and written to.
42 #[cfg(all(feature = "_verify", feature = "nightly"))]
43 pub async fn verify_and_mark_updated<F: AsyncNorFlash>(
44 &mut self,
45 _state_and_dfu_flash: &mut F,
46 _public_key: &[u8],
47 _signature: &[u8],
48 _update_len: u32,
49 _aligned: &mut [u8],
50 ) -> Result<(), FirmwareUpdaterError> {
51 assert_eq!(_aligned.len(), F::WRITE_SIZE);
52 assert!(_update_len <= self.dfu.size());
53
54 #[cfg(feature = "ed25519-dalek")]
55 {
56 use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier};
57
58 use crate::digest_adapters::ed25519_dalek::Sha512;
59
60 let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
61
62 let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
63 let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
64
65 let mut message = [0; 64];
66 self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)
67 .await?;
68
69 public_key.verify(&message, &signature).map_err(into_signature_error)?
70 }
71 #[cfg(feature = "ed25519-salty")]
72 {
73 use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
74 use salty::{PublicKey, Signature};
75
76 use crate::digest_adapters::salty::Sha512;
77
78 fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
79 FirmwareUpdaterError::Signature(signature::Error::default())
80 }
81
82 let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
83 let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
84 let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
85 let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
86
87 let mut message = [0; 64];
88 self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)
89 .await?;
90
91 let r = public_key.verify(&message, &signature);
92 trace!(
93 "Verifying with public key {}, signature {} and message {} yields ok: {}",
94 public_key.to_bytes(),
95 signature.to_bytes(),
96 message,
97 r.is_ok()
98 );
99 r.map_err(into_signature_error)?
100 }
101
102 self.set_magic(_aligned, SWAP_MAGIC, _state_and_dfu_flash).await
103 }
104
105 /// Verify the update in DFU with any digest.
106 pub async fn hash<F: AsyncNorFlash, D: Digest>(
107 &mut self,
108 dfu_flash: &mut F,
109 update_len: u32,
110 chunk_buf: &mut [u8],
111 output: &mut [u8],
112 ) -> Result<(), FirmwareUpdaterError> {
113 let mut digest = D::new();
114 for offset in (0..update_len).step_by(chunk_buf.len()) {
115 self.dfu.read(dfu_flash, offset, chunk_buf).await?;
116 let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len());
117 digest.update(&chunk_buf[..len]);
118 }
119 output.copy_from_slice(digest.finalize().as_slice());
120 Ok(())
121 }
122
123 /// Mark to trigger firmware swap on next boot.
124 ///
125 /// # Safety
126 ///
127 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
128 #[cfg(all(feature = "nightly", not(feature = "_verify")))]
129 pub async fn mark_updated<F: AsyncNorFlash>(
130 &mut self,
131 state_flash: &mut F,
132 aligned: &mut [u8],
133 ) -> Result<(), FirmwareUpdaterError> {
134 assert_eq!(aligned.len(), F::WRITE_SIZE);
135 self.set_magic(aligned, SWAP_MAGIC, state_flash).await
136 }
137
138 /// Mark firmware boot successful and stop rollback on reset.
139 ///
140 /// # Safety
141 ///
142 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
143 pub async fn mark_booted<F: AsyncNorFlash>(
144 &mut self,
145 state_flash: &mut F,
146 aligned: &mut [u8],
147 ) -> Result<(), FirmwareUpdaterError> {
148 assert_eq!(aligned.len(), F::WRITE_SIZE);
149 self.set_magic(aligned, BOOT_MAGIC, state_flash).await
150 }
151
152 async fn set_magic<F: AsyncNorFlash>(
153 &mut self,
154 aligned: &mut [u8],
155 magic: u8,
156 state_flash: &mut F,
157 ) -> Result<(), FirmwareUpdaterError> {
158 self.state.read(state_flash, 0, aligned).await?;
159
160 if aligned.iter().any(|&b| b != magic) {
161 // Read progress validity
162 self.state.read(state_flash, F::WRITE_SIZE as u32, aligned).await?;
163
164 // FIXME: Do not make this assumption.
165 const STATE_ERASE_VALUE: u8 = 0xFF;
166
167 if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
168 // The current progress validity marker is invalid
169 } else {
170 // Invalidate progress
171 aligned.fill(!STATE_ERASE_VALUE);
172 self.state.write(state_flash, F::WRITE_SIZE as u32, aligned).await?;
173 }
174
175 // Clear magic and progress
176 self.state.wipe(state_flash).await?;
177
178 // Set magic
179 aligned.fill(magic);
180 self.state.write(state_flash, 0, aligned).await?;
181 }
182 Ok(())
183 }
184
185 /// Write data to a flash page.
186 ///
187 /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
188 ///
189 /// # Safety
190 ///
191 /// Failing to meet alignment and size requirements may result in a panic.
192 pub async fn write_firmware<F: AsyncNorFlash>(
193 &mut self,
194 offset: usize,
195 data: &[u8],
196 dfu_flash: &mut F,
197 ) -> Result<(), FirmwareUpdaterError> {
198 assert!(data.len() >= F::ERASE_SIZE);
199
200 self.dfu
201 .erase(dfu_flash, offset as u32, (offset + data.len()) as u32)
202 .await?;
203
204 self.dfu.write(dfu_flash, offset as u32, data).await?;
205
206 Ok(())
207 }
208
209 /// Prepare for an incoming DFU update by erasing the entire DFU area and
210 /// returning its `Partition`.
211 ///
212 /// Using this instead of `write_firmware` allows for an optimized API in
213 /// exchange for added complexity.
214 pub async fn prepare_update<F: AsyncNorFlash>(
215 &mut self,
216 dfu_flash: &mut F,
217 ) -> Result<Partition, FirmwareUpdaterError> {
218 self.dfu.wipe(dfu_flash).await?;
219
220 Ok(self.dfu)
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use futures::executor::block_on;
227 use sha1::{Digest, Sha1};
228
229 use super::*;
230 use crate::mem_flash::MemFlash;
231
232 #[test]
233 fn can_verify_sha1() {
234 const STATE: Partition = Partition::new(0, 4096);
235 const DFU: Partition = Partition::new(65536, 131072);
236
237 let mut flash = MemFlash::<131072, 4096, 8>::default();
238
239 let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
240 let mut to_write = [0; 4096];
241 to_write[..7].copy_from_slice(update.as_slice());
242
243 let mut updater = FirmwareUpdater::new(DFU, STATE);
244 block_on(updater.write_firmware(0, to_write.as_slice(), &mut flash)).unwrap();
245 let mut chunk_buf = [0; 2];
246 let mut hash = [0; 20];
247 block_on(updater.hash::<_, Sha1>(&mut flash, update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
248
249 assert_eq!(Sha1::digest(update).as_slice(), hash);
250 }
251}
diff --git a/embassy-boot/boot/src/firmware_updater/blocking.rs b/embassy-boot/boot/src/firmware_updater/blocking.rs
new file mode 100644
index 000000000..50caaf08c
--- /dev/null
+++ b/embassy-boot/boot/src/firmware_updater/blocking.rs
@@ -0,0 +1,221 @@
1use digest::Digest;
2use embedded_storage::nor_flash::NorFlash;
3
4use crate::{FirmwareUpdater, FirmwareUpdaterError, Partition, State, BOOT_MAGIC, SWAP_MAGIC};
5
6impl FirmwareUpdater {
7 /// Create a firmware updater instance with partition ranges for the update and state partitions.
8 pub const fn new(dfu: Partition, state: Partition) -> Self {
9 Self { dfu, state }
10 }
11
12 /// Obtain the current state.
13 ///
14 /// This is useful to check if the bootloader has just done a swap, in order
15 /// to do verifications and self-tests of the new image before calling
16 /// `mark_booted`.
17 pub fn get_state_blocking<F: NorFlash>(
18 &mut self,
19 state_flash: &mut F,
20 aligned: &mut [u8],
21 ) -> Result<State, FirmwareUpdaterError> {
22 self.state.read_blocking(state_flash, 0, aligned)?;
23
24 if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
25 Ok(State::Swap)
26 } else {
27 Ok(State::Boot)
28 }
29 }
30
31 /// Verify the DFU given a public key. If there is an error then DO NOT
32 /// proceed with updating the firmware as it must be signed with a
33 /// corresponding private key (otherwise it could be malicious firmware).
34 ///
35 /// Mark to trigger firmware swap on next boot if verify suceeds.
36 ///
37 /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
38 /// been generated from a SHA-512 digest of the firmware bytes.
39 ///
40 /// If no signature feature is set then this method will always return a
41 /// signature error.
42 ///
43 /// # Safety
44 ///
45 /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from
46 /// and written to.
47 #[cfg(feature = "_verify")]
48 pub fn verify_and_mark_updated_blocking<F: NorFlash>(
49 &mut self,
50 _state_and_dfu_flash: &mut F,
51 _public_key: &[u8],
52 _signature: &[u8],
53 _update_len: u32,
54 _aligned: &mut [u8],
55 ) -> Result<(), FirmwareUpdaterError> {
56 assert_eq!(_aligned.len(), F::WRITE_SIZE);
57 assert!(_update_len <= self.dfu.size());
58
59 #[cfg(feature = "ed25519-dalek")]
60 {
61 use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier};
62
63 use crate::digest_adapters::ed25519_dalek::Sha512;
64
65 let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
66
67 let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
68 let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
69
70 let mut message = [0; 64];
71 self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?;
72
73 public_key.verify(&message, &signature).map_err(into_signature_error)?
74 }
75 #[cfg(feature = "ed25519-salty")]
76 {
77 use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
78 use salty::{PublicKey, Signature};
79
80 use crate::digest_adapters::salty::Sha512;
81
82 fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
83 FirmwareUpdaterError::Signature(signature::Error::default())
84 }
85
86 let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
87 let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
88 let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
89 let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
90
91 let mut message = [0; 64];
92 self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?;
93
94 let r = public_key.verify(&message, &signature);
95 trace!(
96 "Verifying with public key {}, signature {} and message {} yields ok: {}",
97 public_key.to_bytes(),
98 signature.to_bytes(),
99 message,
100 r.is_ok()
101 );
102 r.map_err(into_signature_error)?
103 }
104
105 self.set_magic_blocking(_aligned, SWAP_MAGIC, _state_and_dfu_flash)
106 }
107
108 /// Verify the update in DFU with any digest.
109 pub fn hash_blocking<F: NorFlash, D: Digest>(
110 &mut self,
111 dfu_flash: &mut F,
112 update_len: u32,
113 chunk_buf: &mut [u8],
114 output: &mut [u8],
115 ) -> Result<(), FirmwareUpdaterError> {
116 let mut digest = D::new();
117 for offset in (0..update_len).step_by(chunk_buf.len()) {
118 self.dfu.read_blocking(dfu_flash, offset, chunk_buf)?;
119 let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len());
120 digest.update(&chunk_buf[..len]);
121 }
122 output.copy_from_slice(digest.finalize().as_slice());
123 Ok(())
124 }
125
126 /// Mark to trigger firmware swap on next boot.
127 ///
128 /// # Safety
129 ///
130 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
131 #[cfg(not(feature = "_verify"))]
132 pub fn mark_updated_blocking<F: NorFlash>(
133 &mut self,
134 state_flash: &mut F,
135 aligned: &mut [u8],
136 ) -> Result<(), FirmwareUpdaterError> {
137 assert_eq!(aligned.len(), F::WRITE_SIZE);
138 self.set_magic_blocking(aligned, SWAP_MAGIC, state_flash)
139 }
140
141 /// Mark firmware boot successful and stop rollback on reset.
142 ///
143 /// # Safety
144 ///
145 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
146 pub fn mark_booted_blocking<F: NorFlash>(
147 &mut self,
148 state_flash: &mut F,
149 aligned: &mut [u8],
150 ) -> Result<(), FirmwareUpdaterError> {
151 assert_eq!(aligned.len(), F::WRITE_SIZE);
152 self.set_magic_blocking(aligned, BOOT_MAGIC, state_flash)
153 }
154
155 fn set_magic_blocking<F: NorFlash>(
156 &mut self,
157 aligned: &mut [u8],
158 magic: u8,
159 state_flash: &mut F,
160 ) -> Result<(), FirmwareUpdaterError> {
161 self.state.read_blocking(state_flash, 0, aligned)?;
162
163 if aligned.iter().any(|&b| b != magic) {
164 // Read progress validity
165 self.state.read_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?;
166
167 // FIXME: Do not make this assumption.
168 const STATE_ERASE_VALUE: u8 = 0xFF;
169
170 if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
171 // The current progress validity marker is invalid
172 } else {
173 // Invalidate progress
174 aligned.fill(!STATE_ERASE_VALUE);
175 self.state.write_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?;
176 }
177
178 // Clear magic and progress
179 self.state.wipe_blocking(state_flash)?;
180
181 // Set magic
182 aligned.fill(magic);
183 self.state.write_blocking(state_flash, 0, aligned)?;
184 }
185 Ok(())
186 }
187
188 /// Write data to a flash page.
189 ///
190 /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
191 ///
192 /// # Safety
193 ///
194 /// Failing to meet alignment and size requirements may result in a panic.
195 pub fn write_firmware_blocking<F: NorFlash>(
196 &mut self,
197 offset: usize,
198 data: &[u8],
199 dfu_flash: &mut F,
200 ) -> Result<(), FirmwareUpdaterError> {
201 assert!(data.len() >= F::ERASE_SIZE);
202
203 self.dfu
204 .erase_blocking(dfu_flash, offset as u32, (offset + data.len()) as u32)?;
205
206 self.dfu.write_blocking(dfu_flash, offset as u32, data)?;
207
208 Ok(())
209 }
210
211 /// Prepare for an incoming DFU update by erasing the entire DFU area and
212 /// returning its `Partition`.
213 ///
214 /// Using this instead of `write_firmware_blocking` allows for an optimized
215 /// API in exchange for added complexity.
216 pub fn prepare_update_blocking<F: NorFlash>(&mut self, flash: &mut F) -> Result<Partition, FirmwareUpdaterError> {
217 self.dfu.wipe_blocking(flash)?;
218
219 Ok(self.dfu)
220 }
221}
diff --git a/embassy-boot/boot/src/firmware_updater/mod.rs b/embassy-boot/boot/src/firmware_updater/mod.rs
new file mode 100644
index 000000000..e09f5eb6c
--- /dev/null
+++ b/embassy-boot/boot/src/firmware_updater/mod.rs
@@ -0,0 +1,71 @@
1#[cfg(feature = "nightly")]
2mod asynch;
3mod blocking;
4
5use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind};
6
7use crate::Partition;
8
9/// Errors returned by FirmwareUpdater
10#[derive(Debug)]
11pub enum FirmwareUpdaterError {
12 /// Error from flash.
13 Flash(NorFlashErrorKind),
14 /// Signature errors.
15 Signature(signature::Error),
16}
17
18#[cfg(feature = "defmt")]
19impl defmt::Format for FirmwareUpdaterError {
20 fn format(&self, fmt: defmt::Formatter) {
21 match self {
22 FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"),
23 FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"),
24 }
25 }
26}
27
28impl<E> From<E> for FirmwareUpdaterError
29where
30 E: NorFlashError,
31{
32 fn from(error: E) -> Self {
33 FirmwareUpdaterError::Flash(error.kind())
34 }
35}
36
37/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
38/// 'mess up' the internal bootloader state
39pub struct FirmwareUpdater {
40 state: Partition,
41 dfu: Partition,
42}
43
44#[cfg(target_os = "none")]
45impl Default for FirmwareUpdater {
46 fn default() -> Self {
47 extern "C" {
48 static __bootloader_state_start: u32;
49 static __bootloader_state_end: u32;
50 static __bootloader_dfu_start: u32;
51 static __bootloader_dfu_end: u32;
52 }
53
54 let dfu = unsafe {
55 Partition::new(
56 &__bootloader_dfu_start as *const u32 as u32,
57 &__bootloader_dfu_end as *const u32 as u32,
58 )
59 };
60 let state = unsafe {
61 Partition::new(
62 &__bootloader_state_start as *const u32 as u32,
63 &__bootloader_state_end as *const u32 as u32,
64 )
65 };
66
67 trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
68 trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
69 FirmwareUpdater::new(dfu, state)
70 }
71}