diff options
Diffstat (limited to 'embassy-boot')
| -rw-r--r-- | embassy-boot/boot/Cargo.toml | 4 | ||||
| -rw-r--r-- | embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs | 30 | ||||
| -rw-r--r-- | embassy-boot/boot/src/digest_adapters/mod.rs | 5 | ||||
| -rw-r--r-- | embassy-boot/boot/src/digest_adapters/salty.rs | 29 | ||||
| -rw-r--r-- | embassy-boot/boot/src/firmware_updater.rs | 108 | ||||
| -rw-r--r-- | embassy-boot/boot/src/lib.rs | 1 |
6 files changed, 140 insertions, 37 deletions
diff --git a/embassy-boot/boot/Cargo.toml b/embassy-boot/boot/Cargo.toml index 3312c2f9f..c4ebdaff2 100644 --- a/embassy-boot/boot/Cargo.toml +++ b/embassy-boot/boot/Cargo.toml | |||
| @@ -24,6 +24,7 @@ features = ["defmt"] | |||
| 24 | 24 | ||
| 25 | [dependencies] | 25 | [dependencies] |
| 26 | defmt = { version = "0.3", optional = true } | 26 | defmt = { version = "0.3", optional = true } |
| 27 | digest = "0.10" | ||
| 27 | log = { version = "0.4", optional = true } | 28 | log = { version = "0.4", optional = true } |
| 28 | ed25519-dalek = { version = "1.0.1", default_features = false, features = ["u32_backend"], optional = true } | 29 | ed25519-dalek = { version = "1.0.1", default_features = false, features = ["u32_backend"], optional = true } |
| 29 | embassy-sync = { version = "0.1.0", path = "../../embassy-sync" } | 30 | embassy-sync = { version = "0.1.0", path = "../../embassy-sync" } |
| @@ -37,6 +38,7 @@ log = "0.4" | |||
| 37 | env_logger = "0.9" | 38 | env_logger = "0.9" |
| 38 | rand = "0.7" # ed25519-dalek v1.0.1 depends on this exact version | 39 | rand = "0.7" # ed25519-dalek v1.0.1 depends on this exact version |
| 39 | futures = { version = "0.3", features = ["executor"] } | 40 | futures = { version = "0.3", features = ["executor"] } |
| 41 | sha1 = "0.10.5" | ||
| 40 | 42 | ||
| 41 | [dev-dependencies.ed25519-dalek] | 43 | [dev-dependencies.ed25519-dalek] |
| 42 | default_features = false | 44 | default_features = false |
| @@ -47,4 +49,4 @@ ed25519-dalek = ["dep:ed25519-dalek", "_verify"] | |||
| 47 | ed25519-salty = ["dep:salty", "_verify"] | 49 | ed25519-salty = ["dep:salty", "_verify"] |
| 48 | 50 | ||
| 49 | #Internal features | 51 | #Internal features |
| 50 | _verify = [] \ No newline at end of file | 52 | _verify = [] |
diff --git a/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs b/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs new file mode 100644 index 000000000..a184d1c51 --- /dev/null +++ b/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | use digest::typenum::U64; | ||
| 2 | use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; | ||
| 3 | use ed25519_dalek::Digest as _; | ||
| 4 | |||
| 5 | pub struct Sha512(ed25519_dalek::Sha512); | ||
| 6 | |||
| 7 | impl Default for Sha512 { | ||
| 8 | fn default() -> Self { | ||
| 9 | Self(ed25519_dalek::Sha512::new()) | ||
| 10 | } | ||
| 11 | } | ||
| 12 | |||
| 13 | impl Update for Sha512 { | ||
| 14 | fn update(&mut self, data: &[u8]) { | ||
| 15 | self.0.update(data) | ||
| 16 | } | ||
| 17 | } | ||
| 18 | |||
| 19 | impl FixedOutput for Sha512 { | ||
| 20 | fn finalize_into(self, out: &mut digest::Output<Self>) { | ||
| 21 | let result = self.0.finalize(); | ||
| 22 | out.as_mut_slice().copy_from_slice(result.as_slice()) | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 26 | impl OutputSizeUser for Sha512 { | ||
| 27 | type OutputSize = U64; | ||
| 28 | } | ||
| 29 | |||
| 30 | impl HashMarker for Sha512 {} | ||
diff --git a/embassy-boot/boot/src/digest_adapters/mod.rs b/embassy-boot/boot/src/digest_adapters/mod.rs new file mode 100644 index 000000000..9b4b4b60c --- /dev/null +++ b/embassy-boot/boot/src/digest_adapters/mod.rs | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | #[cfg(feature = "ed25519-dalek")] | ||
| 2 | pub(crate) mod ed25519_dalek; | ||
| 3 | |||
| 4 | #[cfg(feature = "ed25519-salty")] | ||
| 5 | pub(crate) mod salty; | ||
diff --git a/embassy-boot/boot/src/digest_adapters/salty.rs b/embassy-boot/boot/src/digest_adapters/salty.rs new file mode 100644 index 000000000..2b5dcf3af --- /dev/null +++ b/embassy-boot/boot/src/digest_adapters/salty.rs | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | use digest::typenum::U64; | ||
| 2 | use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; | ||
| 3 | |||
| 4 | pub struct Sha512(salty::Sha512); | ||
| 5 | |||
| 6 | impl Default for Sha512 { | ||
| 7 | fn default() -> Self { | ||
| 8 | Self(salty::Sha512::new()) | ||
| 9 | } | ||
| 10 | } | ||
| 11 | |||
| 12 | impl Update for Sha512 { | ||
| 13 | fn update(&mut self, data: &[u8]) { | ||
| 14 | self.0.update(data) | ||
| 15 | } | ||
| 16 | } | ||
| 17 | |||
| 18 | impl FixedOutput for Sha512 { | ||
| 19 | fn finalize_into(self, out: &mut digest::Output<Self>) { | ||
| 20 | let result = self.0.finalize(); | ||
| 21 | out.as_mut_slice().copy_from_slice(result.as_slice()) | ||
| 22 | } | ||
| 23 | } | ||
| 24 | |||
| 25 | impl OutputSizeUser for Sha512 { | ||
| 26 | type OutputSize = U64; | ||
| 27 | } | ||
| 28 | |||
| 29 | impl HashMarker for Sha512 {} | ||
diff --git a/embassy-boot/boot/src/firmware_updater.rs b/embassy-boot/boot/src/firmware_updater.rs index 22e3e6b00..2d1b26980 100644 --- a/embassy-boot/boot/src/firmware_updater.rs +++ b/embassy-boot/boot/src/firmware_updater.rs | |||
| @@ -1,3 +1,4 @@ | |||
| 1 | use digest::Digest; | ||
| 1 | use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; | 2 | use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; |
| 2 | use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; | 3 | use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; |
| 3 | 4 | ||
| @@ -128,25 +129,27 @@ impl FirmwareUpdater { | |||
| 128 | 129 | ||
| 129 | #[cfg(feature = "ed25519-dalek")] | 130 | #[cfg(feature = "ed25519-dalek")] |
| 130 | { | 131 | { |
| 131 | use ed25519_dalek::{Digest, PublicKey, Sha512, Signature, SignatureError, Verifier}; | 132 | use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; |
| 133 | |||
| 134 | use crate::digest_adapters::ed25519_dalek::Sha512; | ||
| 132 | 135 | ||
| 133 | let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); | 136 | let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); |
| 134 | 137 | ||
| 135 | let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; | 138 | let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; |
| 136 | let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; | 139 | let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; |
| 137 | 140 | ||
| 138 | let mut digest = Sha512::new(); | 141 | let mut message = [0; 64]; |
| 139 | self.incremental_hash(_state_and_dfu_flash, _update_len, _aligned, |x| digest.update(x)) | 142 | self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message) |
| 140 | .await?; | 143 | .await?; |
| 141 | 144 | ||
| 142 | public_key | 145 | public_key.verify(&message, &signature).map_err(into_signature_error)? |
| 143 | .verify(&digest.finalize(), &signature) | ||
| 144 | .map_err(into_signature_error)? | ||
| 145 | } | 146 | } |
| 146 | #[cfg(feature = "ed25519-salty")] | 147 | #[cfg(feature = "ed25519-salty")] |
| 147 | { | 148 | { |
| 148 | use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; | 149 | use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; |
| 149 | use salty::{PublicKey, Sha512, Signature}; | 150 | use salty::{PublicKey, Signature}; |
| 151 | |||
| 152 | use crate::digest_adapters::salty::Sha512; | ||
| 150 | 153 | ||
| 151 | fn into_signature_error<E>(_: E) -> FirmwareUpdaterError { | 154 | fn into_signature_error<E>(_: E) -> FirmwareUpdaterError { |
| 152 | FirmwareUpdaterError::Signature(signature::Error::default()) | 155 | FirmwareUpdaterError::Signature(signature::Error::default()) |
| @@ -157,11 +160,10 @@ impl FirmwareUpdater { | |||
| 157 | let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; | 160 | let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; |
| 158 | let signature = Signature::try_from(&signature).map_err(into_signature_error)?; | 161 | let signature = Signature::try_from(&signature).map_err(into_signature_error)?; |
| 159 | 162 | ||
| 160 | let mut digest = Sha512::new(); | 163 | let mut message = [0; 64]; |
| 161 | self.incremental_hash(_state_and_dfu_flash, _update_len, _aligned, |x| digest.update(x)) | 164 | self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message) |
| 162 | .await?; | 165 | .await?; |
| 163 | 166 | ||
| 164 | let message = digest.finalize(); | ||
| 165 | let r = public_key.verify(&message, &signature); | 167 | let r = public_key.verify(&message, &signature); |
| 166 | trace!( | 168 | trace!( |
| 167 | "Verifying with public key {}, signature {} and message {} yields ok: {}", | 169 | "Verifying with public key {}, signature {} and message {} yields ok: {}", |
| @@ -176,19 +178,21 @@ impl FirmwareUpdater { | |||
| 176 | self.set_magic(_aligned, SWAP_MAGIC, _state_and_dfu_flash).await | 178 | self.set_magic(_aligned, SWAP_MAGIC, _state_and_dfu_flash).await |
| 177 | } | 179 | } |
| 178 | 180 | ||
| 179 | /// Iterate through the DFU and process all bytes with the provided closure. | 181 | /// Verify the update in DFU with any digest. |
| 180 | pub async fn incremental_hash<F: AsyncNorFlash>( | 182 | pub async fn hash<F: AsyncNorFlash, D: Digest>( |
| 181 | &mut self, | 183 | &mut self, |
| 182 | dfu_flash: &mut F, | 184 | dfu_flash: &mut F, |
| 183 | update_len: u32, | 185 | update_len: u32, |
| 184 | aligned: &mut [u8], | 186 | chunk_buf: &mut [u8], |
| 185 | mut update: impl FnMut(&[u8]), | 187 | output: &mut [u8], |
| 186 | ) -> Result<(), FirmwareUpdaterError> { | 188 | ) -> Result<(), FirmwareUpdaterError> { |
| 187 | for offset in (0..update_len).step_by(aligned.len()) { | 189 | let mut digest = D::new(); |
| 188 | self.dfu.read(dfu_flash, offset, aligned).await?; | 190 | for offset in (0..update_len).step_by(chunk_buf.len()) { |
| 189 | let len = core::cmp::min((update_len - offset) as usize, aligned.len()); | 191 | self.dfu.read(dfu_flash, offset, chunk_buf).await?; |
| 190 | update(&aligned[..len]); | 192 | let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); |
| 193 | digest.update(&chunk_buf[..len]); | ||
| 191 | } | 194 | } |
| 195 | output.copy_from_slice(digest.finalize().as_slice()); | ||
| 192 | Ok(()) | 196 | Ok(()) |
| 193 | } | 197 | } |
| 194 | 198 | ||
| @@ -334,24 +338,26 @@ impl FirmwareUpdater { | |||
| 334 | 338 | ||
| 335 | #[cfg(feature = "ed25519-dalek")] | 339 | #[cfg(feature = "ed25519-dalek")] |
| 336 | { | 340 | { |
| 337 | use ed25519_dalek::{Digest, PublicKey, Sha512, Signature, SignatureError, Verifier}; | 341 | use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; |
| 342 | |||
| 343 | use crate::digest_adapters::ed25519_dalek::Sha512; | ||
| 338 | 344 | ||
| 339 | let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); | 345 | let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); |
| 340 | 346 | ||
| 341 | let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; | 347 | let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; |
| 342 | let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; | 348 | let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; |
| 343 | 349 | ||
| 344 | let mut digest = Sha512::new(); | 350 | let mut message = [0; 64]; |
| 345 | self.incremental_hash_blocking(_state_and_dfu_flash, _update_len, _aligned, |x| digest.update(x))?; | 351 | self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?; |
| 346 | 352 | ||
| 347 | public_key | 353 | public_key.verify(&message, &signature).map_err(into_signature_error)? |
| 348 | .verify(&digest.finalize(), &signature) | ||
| 349 | .map_err(into_signature_error)? | ||
| 350 | } | 354 | } |
| 351 | #[cfg(feature = "ed25519-salty")] | 355 | #[cfg(feature = "ed25519-salty")] |
| 352 | { | 356 | { |
| 353 | use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; | 357 | use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; |
| 354 | use salty::{PublicKey, Sha512, Signature}; | 358 | use salty::{PublicKey, Signature}; |
| 359 | |||
| 360 | use crate::digest_adapters::salty::Sha512; | ||
| 355 | 361 | ||
| 356 | fn into_signature_error<E>(_: E) -> FirmwareUpdaterError { | 362 | fn into_signature_error<E>(_: E) -> FirmwareUpdaterError { |
| 357 | FirmwareUpdaterError::Signature(signature::Error::default()) | 363 | FirmwareUpdaterError::Signature(signature::Error::default()) |
| @@ -362,10 +368,9 @@ impl FirmwareUpdater { | |||
| 362 | let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; | 368 | let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; |
| 363 | let signature = Signature::try_from(&signature).map_err(into_signature_error)?; | 369 | let signature = Signature::try_from(&signature).map_err(into_signature_error)?; |
| 364 | 370 | ||
| 365 | let mut digest = Sha512::new(); | 371 | let mut message = [0; 64]; |
| 366 | self.incremental_hash_blocking(_state_and_dfu_flash, _update_len, _aligned, |x| digest.update(x))?; | 372 | self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?; |
| 367 | 373 | ||
| 368 | let message = digest.finalize(); | ||
| 369 | let r = public_key.verify(&message, &signature); | 374 | let r = public_key.verify(&message, &signature); |
| 370 | trace!( | 375 | trace!( |
| 371 | "Verifying with public key {}, signature {} and message {} yields ok: {}", | 376 | "Verifying with public key {}, signature {} and message {} yields ok: {}", |
| @@ -380,19 +385,21 @@ impl FirmwareUpdater { | |||
| 380 | self.set_magic_blocking(_aligned, SWAP_MAGIC, _state_and_dfu_flash) | 385 | self.set_magic_blocking(_aligned, SWAP_MAGIC, _state_and_dfu_flash) |
| 381 | } | 386 | } |
| 382 | 387 | ||
| 383 | /// Iterate through the DFU and process all bytes with the provided closure. | 388 | /// Verify the update in DFU with any digest. |
| 384 | pub fn incremental_hash_blocking<F: NorFlash>( | 389 | pub fn hash_blocking<F: NorFlash, D: Digest>( |
| 385 | &mut self, | 390 | &mut self, |
| 386 | dfu_flash: &mut F, | 391 | dfu_flash: &mut F, |
| 387 | update_len: u32, | 392 | update_len: u32, |
| 388 | aligned: &mut [u8], | 393 | chunk_buf: &mut [u8], |
| 389 | mut update: impl FnMut(&[u8]), | 394 | output: &mut [u8], |
| 390 | ) -> Result<(), FirmwareUpdaterError> { | 395 | ) -> Result<(), FirmwareUpdaterError> { |
| 391 | for offset in (0..update_len).step_by(aligned.len()) { | 396 | let mut digest = D::new(); |
| 392 | self.dfu.read_blocking(dfu_flash, offset, aligned)?; | 397 | for offset in (0..update_len).step_by(chunk_buf.len()) { |
| 393 | let len = core::cmp::min((update_len - offset) as usize, aligned.len()); | 398 | self.dfu.read_blocking(dfu_flash, offset, chunk_buf)?; |
| 394 | update(&aligned[..len]); | 399 | let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); |
| 400 | digest.update(&chunk_buf[..len]); | ||
| 395 | } | 401 | } |
| 402 | output.copy_from_slice(digest.finalize().as_slice()); | ||
| 396 | Ok(()) | 403 | Ok(()) |
| 397 | } | 404 | } |
| 398 | 405 | ||
| @@ -479,3 +486,32 @@ impl FirmwareUpdater { | |||
| 479 | Ok(self.dfu) | 486 | Ok(self.dfu) |
| 480 | } | 487 | } |
| 481 | } | 488 | } |
| 489 | |||
| 490 | #[cfg(test)] | ||
| 491 | mod tests { | ||
| 492 | use futures::executor::block_on; | ||
| 493 | use sha1::{Digest, Sha1}; | ||
| 494 | |||
| 495 | use super::*; | ||
| 496 | use crate::tests::MemFlash; | ||
| 497 | |||
| 498 | #[test] | ||
| 499 | fn can_verify() { | ||
| 500 | const STATE: Partition = Partition::new(0, 4096); | ||
| 501 | const DFU: Partition = Partition::new(65536, 131072); | ||
| 502 | |||
| 503 | let mut flash = MemFlash::<131072, 4096, 8>([0xFF; 131072]); | ||
| 504 | |||
| 505 | let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; | ||
| 506 | let mut to_write = [0; 4096]; | ||
| 507 | to_write[..7].copy_from_slice(update.as_slice()); | ||
| 508 | |||
| 509 | let mut updater = FirmwareUpdater::new(DFU, STATE); | ||
| 510 | block_on(updater.write_firmware(0, to_write.as_slice(), &mut flash)).unwrap(); | ||
| 511 | let mut chunk_buf = [0; 2]; | ||
| 512 | let mut hash = [0; 20]; | ||
| 513 | block_on(updater.hash::<_, Sha1>(&mut flash, update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); | ||
| 514 | |||
| 515 | assert_eq!(Sha1::digest(update).as_slice(), hash); | ||
| 516 | } | ||
| 517 | } | ||
diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 6888a8055..da9055476 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs | |||
| @@ -6,6 +6,7 @@ | |||
| 6 | mod fmt; | 6 | mod fmt; |
| 7 | 7 | ||
| 8 | mod boot_loader; | 8 | mod boot_loader; |
| 9 | mod digest_adapters; | ||
| 9 | mod firmware_updater; | 10 | mod firmware_updater; |
| 10 | mod partition; | 11 | mod partition; |
| 11 | 12 | ||
