diff options
Diffstat (limited to 'embassy-boot/src/firmware_updater/asynch.rs')
| -rw-r--r-- | embassy-boot/src/firmware_updater/asynch.rs | 329 |
1 files changed, 329 insertions, 0 deletions
diff --git a/embassy-boot/src/firmware_updater/asynch.rs b/embassy-boot/src/firmware_updater/asynch.rs new file mode 100644 index 000000000..2e43e1cc1 --- /dev/null +++ b/embassy-boot/src/firmware_updater/asynch.rs | |||
| @@ -0,0 +1,329 @@ | |||
| 1 | use digest::Digest; | ||
| 2 | #[cfg(target_os = "none")] | ||
| 3 | use embassy_embedded_hal::flash::partition::Partition; | ||
| 4 | #[cfg(target_os = "none")] | ||
| 5 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||
| 6 | use embedded_storage_async::nor_flash::NorFlash; | ||
| 7 | |||
| 8 | use super::FirmwareUpdaterConfig; | ||
| 9 | use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; | ||
| 10 | |||
| 11 | /// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to | ||
| 12 | /// 'mess up' the internal bootloader state | ||
| 13 | pub struct FirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { | ||
| 14 | dfu: DFU, | ||
| 15 | state: FirmwareState<'d, STATE>, | ||
| 16 | } | ||
| 17 | |||
| 18 | #[cfg(target_os = "none")] | ||
| 19 | impl<'a, FLASH: NorFlash> | ||
| 20 | FirmwareUpdaterConfig<Partition<'a, NoopRawMutex, FLASH>, Partition<'a, NoopRawMutex, FLASH>> | ||
| 21 | { | ||
| 22 | /// Create a firmware updater config from the flash and address symbols defined in the linkerfile | ||
| 23 | pub fn from_linkerfile(flash: &'a embassy_sync::mutex::Mutex<NoopRawMutex, FLASH>) -> Self { | ||
| 24 | extern "C" { | ||
| 25 | static __bootloader_state_start: u32; | ||
| 26 | static __bootloader_state_end: u32; | ||
| 27 | static __bootloader_dfu_start: u32; | ||
| 28 | static __bootloader_dfu_end: u32; | ||
| 29 | } | ||
| 30 | |||
| 31 | let dfu = unsafe { | ||
| 32 | let start = &__bootloader_dfu_start as *const u32 as u32; | ||
| 33 | let end = &__bootloader_dfu_end as *const u32 as u32; | ||
| 34 | trace!("DFU: 0x{:x} - 0x{:x}", start, end); | ||
| 35 | |||
| 36 | Partition::new(flash, start, end - start) | ||
| 37 | }; | ||
| 38 | let state = unsafe { | ||
| 39 | let start = &__bootloader_state_start as *const u32 as u32; | ||
| 40 | let end = &__bootloader_state_end as *const u32 as u32; | ||
| 41 | trace!("STATE: 0x{:x} - 0x{:x}", start, end); | ||
| 42 | |||
| 43 | Partition::new(flash, start, end - start) | ||
| 44 | }; | ||
| 45 | |||
| 46 | Self { dfu, state } | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { | ||
| 51 | /// Create a firmware updater instance with partition ranges for the update and state partitions. | ||
| 52 | pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self { | ||
| 53 | Self { | ||
| 54 | dfu: config.dfu, | ||
| 55 | state: FirmwareState::new(config.state, aligned), | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | /// Obtain the current state. | ||
| 60 | /// | ||
| 61 | /// This is useful to check if the bootloader has just done a swap, in order | ||
| 62 | /// to do verifications and self-tests of the new image before calling | ||
| 63 | /// `mark_booted`. | ||
| 64 | pub async fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> { | ||
| 65 | self.state.get_state().await | ||
| 66 | } | ||
| 67 | |||
| 68 | /// Verify the DFU given a public key. If there is an error then DO NOT | ||
| 69 | /// proceed with updating the firmware as it must be signed with a | ||
| 70 | /// corresponding private key (otherwise it could be malicious firmware). | ||
| 71 | /// | ||
| 72 | /// Mark to trigger firmware swap on next boot if verify suceeds. | ||
| 73 | /// | ||
| 74 | /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have | ||
| 75 | /// been generated from a SHA-512 digest of the firmware bytes. | ||
| 76 | /// | ||
| 77 | /// If no signature feature is set then this method will always return a | ||
| 78 | /// signature error. | ||
| 79 | #[cfg(feature = "_verify")] | ||
| 80 | pub async fn verify_and_mark_updated( | ||
| 81 | &mut self, | ||
| 82 | _public_key: &[u8; 32], | ||
| 83 | _signature: &[u8; 64], | ||
| 84 | _update_len: u32, | ||
| 85 | ) -> Result<(), FirmwareUpdaterError> { | ||
| 86 | assert!(_update_len <= self.dfu.capacity() as u32); | ||
| 87 | |||
| 88 | self.state.verify_booted().await?; | ||
| 89 | |||
| 90 | #[cfg(feature = "ed25519-dalek")] | ||
| 91 | { | ||
| 92 | use ed25519_dalek::{Signature, SignatureError, Verifier, VerifyingKey}; | ||
| 93 | |||
| 94 | use crate::digest_adapters::ed25519_dalek::Sha512; | ||
| 95 | |||
| 96 | let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); | ||
| 97 | |||
| 98 | let public_key = VerifyingKey::from_bytes(_public_key).map_err(into_signature_error)?; | ||
| 99 | let signature = Signature::from_bytes(_signature); | ||
| 100 | |||
| 101 | let mut chunk_buf = [0; 2]; | ||
| 102 | let mut message = [0; 64]; | ||
| 103 | self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message).await?; | ||
| 104 | |||
| 105 | public_key.verify(&message, &signature).map_err(into_signature_error)? | ||
| 106 | } | ||
| 107 | #[cfg(feature = "ed25519-salty")] | ||
| 108 | { | ||
| 109 | use salty::{PublicKey, Signature}; | ||
| 110 | |||
| 111 | use crate::digest_adapters::salty::Sha512; | ||
| 112 | |||
| 113 | fn into_signature_error<E>(_: E) -> FirmwareUpdaterError { | ||
| 114 | FirmwareUpdaterError::Signature(signature::Error::default()) | ||
| 115 | } | ||
| 116 | |||
| 117 | let public_key = PublicKey::try_from(_public_key).map_err(into_signature_error)?; | ||
| 118 | let signature = Signature::try_from(_signature).map_err(into_signature_error)?; | ||
| 119 | |||
| 120 | let mut message = [0; 64]; | ||
| 121 | let mut chunk_buf = [0; 2]; | ||
| 122 | self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message).await?; | ||
| 123 | |||
| 124 | let r = public_key.verify(&message, &signature); | ||
| 125 | trace!( | ||
| 126 | "Verifying with public key {}, signature {} and message {} yields ok: {}", | ||
| 127 | public_key.to_bytes(), | ||
| 128 | signature.to_bytes(), | ||
| 129 | message, | ||
| 130 | r.is_ok() | ||
| 131 | ); | ||
| 132 | r.map_err(into_signature_error)? | ||
| 133 | } | ||
| 134 | |||
| 135 | self.state.mark_updated().await | ||
| 136 | } | ||
| 137 | |||
| 138 | /// Verify the update in DFU with any digest. | ||
| 139 | pub async fn hash<D: Digest>( | ||
| 140 | &mut self, | ||
| 141 | update_len: u32, | ||
| 142 | chunk_buf: &mut [u8], | ||
| 143 | output: &mut [u8], | ||
| 144 | ) -> Result<(), FirmwareUpdaterError> { | ||
| 145 | let mut digest = D::new(); | ||
| 146 | for offset in (0..update_len).step_by(chunk_buf.len()) { | ||
| 147 | self.dfu.read(offset, chunk_buf).await?; | ||
| 148 | let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); | ||
| 149 | digest.update(&chunk_buf[..len]); | ||
| 150 | } | ||
| 151 | output.copy_from_slice(digest.finalize().as_slice()); | ||
| 152 | Ok(()) | ||
| 153 | } | ||
| 154 | |||
| 155 | /// Mark to trigger firmware swap on next boot. | ||
| 156 | #[cfg(not(feature = "_verify"))] | ||
| 157 | pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 158 | self.state.mark_updated().await | ||
| 159 | } | ||
| 160 | |||
| 161 | /// Mark to trigger USB DFU on next boot. | ||
| 162 | pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 163 | self.state.verify_booted().await?; | ||
| 164 | self.state.mark_dfu().await | ||
| 165 | } | ||
| 166 | |||
| 167 | /// Mark firmware boot successful and stop rollback on reset. | ||
| 168 | pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 169 | self.state.mark_booted().await | ||
| 170 | } | ||
| 171 | |||
| 172 | /// Write data to a flash page. | ||
| 173 | /// | ||
| 174 | /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. | ||
| 175 | /// | ||
| 176 | /// # Safety | ||
| 177 | /// | ||
| 178 | /// Failing to meet alignment and size requirements may result in a panic. | ||
| 179 | pub async fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { | ||
| 180 | assert!(data.len() >= DFU::ERASE_SIZE); | ||
| 181 | |||
| 182 | self.state.verify_booted().await?; | ||
| 183 | |||
| 184 | self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?; | ||
| 185 | |||
| 186 | self.dfu.write(offset as u32, data).await?; | ||
| 187 | |||
| 188 | Ok(()) | ||
| 189 | } | ||
| 190 | |||
| 191 | /// Prepare for an incoming DFU update by erasing the entire DFU area and | ||
| 192 | /// returning its `Partition`. | ||
| 193 | /// | ||
| 194 | /// Using this instead of `write_firmware` allows for an optimized API in | ||
| 195 | /// exchange for added complexity. | ||
| 196 | pub async fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> { | ||
| 197 | self.state.verify_booted().await?; | ||
| 198 | self.dfu.erase(0, self.dfu.capacity() as u32).await?; | ||
| 199 | |||
| 200 | Ok(&mut self.dfu) | ||
| 201 | } | ||
| 202 | } | ||
| 203 | |||
| 204 | /// Manages the state partition of the firmware update. | ||
| 205 | /// | ||
| 206 | /// Can be used standalone for more fine grained control, or as part of the updater. | ||
| 207 | pub struct FirmwareState<'d, STATE> { | ||
| 208 | state: STATE, | ||
| 209 | aligned: &'d mut [u8], | ||
| 210 | } | ||
| 211 | |||
| 212 | impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { | ||
| 213 | /// Create a firmware state instance from a FirmwareUpdaterConfig with a buffer for magic content and state partition. | ||
| 214 | /// | ||
| 215 | /// # Safety | ||
| 216 | /// | ||
| 217 | /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from | ||
| 218 | /// and written to. | ||
| 219 | pub fn from_config<DFU: NorFlash>(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self { | ||
| 220 | Self::new(config.state, aligned) | ||
| 221 | } | ||
| 222 | |||
| 223 | /// Create a firmware state instance with a buffer for magic content and state partition. | ||
| 224 | /// | ||
| 225 | /// # Safety | ||
| 226 | /// | ||
| 227 | /// The `aligned` buffer must have a size of maximum of STATE::WRITE_SIZE and STATE::READ_SIZE, | ||
| 228 | /// and follow the alignment rules for the flash being read from and written to. | ||
| 229 | pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self { | ||
| 230 | assert_eq!(aligned.len(), STATE::WRITE_SIZE.max(STATE::READ_SIZE)); | ||
| 231 | Self { state, aligned } | ||
| 232 | } | ||
| 233 | |||
| 234 | // Make sure we are running a booted firmware to avoid reverting to a bad state. | ||
| 235 | async fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 236 | if self.get_state().await? == State::Boot { | ||
| 237 | Ok(()) | ||
| 238 | } else { | ||
| 239 | Err(FirmwareUpdaterError::BadState) | ||
| 240 | } | ||
| 241 | } | ||
| 242 | |||
| 243 | /// Obtain the current state. | ||
| 244 | /// | ||
| 245 | /// This is useful to check if the bootloader has just done a swap, in order | ||
| 246 | /// to do verifications and self-tests of the new image before calling | ||
| 247 | /// `mark_booted`. | ||
| 248 | pub async fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> { | ||
| 249 | self.state.read(0, &mut self.aligned).await?; | ||
| 250 | |||
| 251 | if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { | ||
| 252 | Ok(State::Swap) | ||
| 253 | } else { | ||
| 254 | Ok(State::Boot) | ||
| 255 | } | ||
| 256 | } | ||
| 257 | |||
| 258 | /// Mark to trigger firmware swap on next boot. | ||
| 259 | pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 260 | self.set_magic(SWAP_MAGIC).await | ||
| 261 | } | ||
| 262 | |||
| 263 | /// Mark to trigger USB DFU on next boot. | ||
| 264 | pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 265 | self.set_magic(DFU_DETACH_MAGIC).await | ||
| 266 | } | ||
| 267 | |||
| 268 | /// Mark firmware boot successful and stop rollback on reset. | ||
| 269 | pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 270 | self.set_magic(BOOT_MAGIC).await | ||
| 271 | } | ||
| 272 | |||
| 273 | async fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> { | ||
| 274 | self.state.read(0, &mut self.aligned).await?; | ||
| 275 | |||
| 276 | if self.aligned.iter().any(|&b| b != magic) { | ||
| 277 | // Read progress validity | ||
| 278 | self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned).await?; | ||
| 279 | |||
| 280 | if self.aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { | ||
| 281 | // The current progress validity marker is invalid | ||
| 282 | } else { | ||
| 283 | // Invalidate progress | ||
| 284 | self.aligned.fill(!STATE_ERASE_VALUE); | ||
| 285 | self.state.write(STATE::WRITE_SIZE as u32, &self.aligned).await?; | ||
| 286 | } | ||
| 287 | |||
| 288 | // Clear magic and progress | ||
| 289 | self.state.erase(0, self.state.capacity() as u32).await?; | ||
| 290 | |||
| 291 | // Set magic | ||
| 292 | self.aligned.fill(magic); | ||
| 293 | self.state.write(0, &self.aligned).await?; | ||
| 294 | } | ||
| 295 | Ok(()) | ||
| 296 | } | ||
| 297 | } | ||
| 298 | |||
| 299 | #[cfg(test)] | ||
| 300 | mod tests { | ||
| 301 | use embassy_embedded_hal::flash::partition::Partition; | ||
| 302 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||
| 303 | use embassy_sync::mutex::Mutex; | ||
| 304 | use futures::executor::block_on; | ||
| 305 | use sha1::{Digest, Sha1}; | ||
| 306 | |||
| 307 | use super::*; | ||
| 308 | use crate::mem_flash::MemFlash; | ||
| 309 | |||
| 310 | #[test] | ||
| 311 | fn can_verify_sha1() { | ||
| 312 | let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 4096, 8>::default()); | ||
| 313 | let state = Partition::new(&flash, 0, 4096); | ||
| 314 | let dfu = Partition::new(&flash, 65536, 65536); | ||
| 315 | let mut aligned = [0; 8]; | ||
| 316 | |||
| 317 | let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; | ||
| 318 | let mut to_write = [0; 4096]; | ||
| 319 | to_write[..7].copy_from_slice(update.as_slice()); | ||
| 320 | |||
| 321 | let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); | ||
| 322 | block_on(updater.write_firmware(0, to_write.as_slice())).unwrap(); | ||
| 323 | let mut chunk_buf = [0; 2]; | ||
| 324 | let mut hash = [0; 20]; | ||
| 325 | block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); | ||
| 326 | |||
| 327 | assert_eq!(Sha1::digest(update).as_slice(), hash); | ||
| 328 | } | ||
| 329 | } | ||
