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