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"] [dependencies] defmt = { version = "0.3", optional = true } +digest = "0.10" log = { version = "0.4", optional = true } ed25519-dalek = { version = "1.0.1", default_features = false, features = ["u32_backend"], optional = true } embassy-sync = { version = "0.1.0", path = "../../embassy-sync" } @@ -37,6 +38,7 @@ log = "0.4" env_logger = "0.9" rand = "0.7" # ed25519-dalek v1.0.1 depends on this exact version futures = { version = "0.3", features = ["executor"] } +sha1 = "0.10.5" [dev-dependencies.ed25519-dalek] default_features = false @@ -47,4 +49,4 @@ ed25519-dalek = ["dep:ed25519-dalek", "_verify"] ed25519-salty = ["dep:salty", "_verify"] #Internal features -_verify = [] \ No newline at end of file +_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 @@ +use digest::typenum::U64; +use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; +use ed25519_dalek::Digest as _; + +pub struct Sha512(ed25519_dalek::Sha512); + +impl Default for Sha512 { + fn default() -> Self { + Self(ed25519_dalek::Sha512::new()) + } +} + +impl Update for Sha512 { + fn update(&mut self, data: &[u8]) { + self.0.update(data) + } +} + +impl FixedOutput for Sha512 { + fn finalize_into(self, out: &mut digest::Output) { + let result = self.0.finalize(); + out.as_mut_slice().copy_from_slice(result.as_slice()) + } +} + +impl OutputSizeUser for Sha512 { + type OutputSize = U64; +} + +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 @@ +#[cfg(feature = "ed25519-dalek")] +pub(crate) mod ed25519_dalek; + +#[cfg(feature = "ed25519-salty")] +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 @@ +use digest::typenum::U64; +use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; + +pub struct Sha512(salty::Sha512); + +impl Default for Sha512 { + fn default() -> Self { + Self(salty::Sha512::new()) + } +} + +impl Update for Sha512 { + fn update(&mut self, data: &[u8]) { + self.0.update(data) + } +} + +impl FixedOutput for Sha512 { + fn finalize_into(self, out: &mut digest::Output) { + let result = self.0.finalize(); + out.as_mut_slice().copy_from_slice(result.as_slice()) + } +} + +impl OutputSizeUser for Sha512 { + type OutputSize = U64; +} + +impl HashMarker for Sha512 {} diff --git a/embassy-boot/boot/src/firmware_updater.rs b/embassy-boot/boot/src/firmware_updater.rs index 2b5cc72fa..48e15024e 100644 --- a/embassy-boot/boot/src/firmware_updater.rs +++ b/embassy-boot/boot/src/firmware_updater.rs @@ -1,3 +1,4 @@ +use digest::Digest; use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; @@ -121,35 +122,32 @@ impl FirmwareUpdater { _update_len: usize, _aligned: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { - let _read_size = _aligned.len(); - assert_eq!(_aligned.len(), F::WRITE_SIZE); assert!(_update_len <= self.dfu.len()); #[cfg(feature = "ed25519-dalek")] { - use ed25519_dalek::{Digest, PublicKey, Sha512, Signature, SignatureError, Verifier}; + use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; + + use crate::digest_adapters::ed25519_dalek::Sha512; let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; - let mut digest = Sha512::new(); - for offset in (0.._update_len).step_by(_aligned.len()) { - self.dfu.read(_state_and_dfu_flash, offset as u32, _aligned).await?; - let len = core::cmp::min(_update_len - offset, _aligned.len()); - digest.update(&_aligned[..len]); - } + let mut message = [0; 64]; + self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message) + .await?; - public_key - .verify(&digest.finalize(), &signature) - .map_err(into_signature_error)? + public_key.verify(&message, &signature).map_err(into_signature_error)? } #[cfg(feature = "ed25519-salty")] { use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; - use salty::{PublicKey, Sha512, Signature}; + use salty::{PublicKey, Signature}; + + use crate::digest_adapters::salty::Sha512; fn into_signature_error(_: E) -> FirmwareUpdaterError { FirmwareUpdaterError::Signature(signature::Error::default()) @@ -160,14 +158,10 @@ impl FirmwareUpdater { let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; let signature = Signature::try_from(&signature).map_err(into_signature_error)?; - let mut digest = Sha512::new(); - for offset in (0.._update_len).step_by(_aligned.len()) { - self.dfu.read(_state_and_dfu_flash, offset as u32, _aligned).await?; - let len = core::cmp::min(_update_len - offset, _aligned.len()); - digest.update(&_aligned[..len]); - } + let mut message = [0; 64]; + self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message) + .await?; - let message = digest.finalize(); let r = public_key.verify(&message, &signature); trace!( "Verifying with public key {}, signature {} and message {} yields ok: {}", @@ -182,6 +176,25 @@ impl FirmwareUpdater { self.set_magic(_aligned, SWAP_MAGIC, _state_and_dfu_flash).await } + /// Verify the update in DFU with any digest. + pub async fn hash( + &mut self, + dfu_flash: &mut F, + update_len: usize, + chunk_buf: &mut [u8], + output: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + let update_len = update_len as u32; + let mut digest = D::new(); + for offset in (0..update_len).step_by(chunk_buf.len()) { + self.dfu.read(dfu_flash, offset, chunk_buf).await?; + let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); + digest.update(&chunk_buf[..len]); + } + output.copy_from_slice(digest.finalize().as_slice()); + Ok(()) + } + /// Mark to trigger firmware swap on next boot. /// /// # Safety @@ -330,36 +343,31 @@ impl FirmwareUpdater { _update_len: usize, _aligned: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { - let _end = self.dfu.from + _update_len; - let _read_size = _aligned.len(); - assert_eq!(_aligned.len(), F::WRITE_SIZE); - assert!(_end <= self.dfu.to); + assert!(_update_len <= self.dfu.len()); #[cfg(feature = "ed25519-dalek")] { - use ed25519_dalek::{Digest, PublicKey, Sha512, Signature, SignatureError, Verifier}; + use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; + + use crate::digest_adapters::ed25519_dalek::Sha512; let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; - let mut digest = Sha512::new(); - for offset in (0.._update_len).step_by(_aligned.len()) { - self.dfu.read_blocking(_state_and_dfu_flash, offset as u32, _aligned)?; - let len = core::cmp::min(_update_len - offset, _aligned.len()); - digest.update(&_aligned[..len]); - } + let mut message = [0; 64]; + self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?; - public_key - .verify(&digest.finalize(), &signature) - .map_err(into_signature_error)? + public_key.verify(&message, &signature).map_err(into_signature_error)? } #[cfg(feature = "ed25519-salty")] { use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; - use salty::{PublicKey, Sha512, Signature}; + use salty::{PublicKey, Signature}; + + use crate::digest_adapters::salty::Sha512; fn into_signature_error(_: E) -> FirmwareUpdaterError { FirmwareUpdaterError::Signature(signature::Error::default()) @@ -370,14 +378,9 @@ impl FirmwareUpdater { let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; let signature = Signature::try_from(&signature).map_err(into_signature_error)?; - let mut digest = Sha512::new(); - for offset in (0.._update_len).step_by(_aligned.len()) { - self.dfu.read_blocking(_state_and_dfu_flash, offset as u32, _aligned)?; - let len = core::cmp::min(_update_len - offset, _aligned.len()); - digest.update(&_aligned[..len]); - } + let mut message = [0; 64]; + self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?; - let message = digest.finalize(); let r = public_key.verify(&message, &signature); trace!( "Verifying with public key {}, signature {} and message {} yields ok: {}", @@ -392,6 +395,25 @@ impl FirmwareUpdater { self.set_magic_blocking(_aligned, SWAP_MAGIC, _state_and_dfu_flash) } + /// Verify the update in DFU with any digest. + pub fn hash_blocking( + &mut self, + dfu_flash: &mut F, + update_len: usize, + chunk_buf: &mut [u8], + output: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + let update_len = update_len as u32; + let mut digest = D::new(); + for offset in (0..update_len).step_by(chunk_buf.len()) { + self.dfu.read_blocking(dfu_flash, offset, chunk_buf)?; + let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); + digest.update(&chunk_buf[..len]); + } + output.copy_from_slice(digest.finalize().as_slice()); + Ok(()) + } + /// Mark to trigger firmware swap on next boot. /// /// # Safety @@ -488,3 +510,32 @@ impl FirmwareUpdater { Ok(self.dfu) } } + +#[cfg(test)] +mod tests { + use futures::executor::block_on; + use sha1::{Digest, Sha1}; + + use super::*; + use crate::mem_flash::MemFlash; + + #[test] + fn can_verify_sha1() { + const STATE: Partition = Partition::new(0, 4096); + const DFU: Partition = Partition::new(65536, 131072); + + let mut flash = MemFlash::<131072, 4096, 8>::default(); + + let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; + let mut to_write = [0; 4096]; + to_write[..7].copy_from_slice(update.as_slice()); + + let mut updater = FirmwareUpdater::new(DFU, STATE); + block_on(updater.write_firmware(0, to_write.as_slice(), &mut flash)).unwrap(); + let mut chunk_buf = [0; 2]; + let mut hash = [0; 20]; + block_on(updater.hash::<_, Sha1>(&mut flash, update.len(), &mut chunk_buf, &mut hash)).unwrap(); + + assert_eq!(Sha1::digest(update).as_slice(), hash); + } +} diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 3109f2b47..acd90996f 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -6,6 +6,7 @@ mod fmt; mod boot_loader; +mod digest_adapters; mod firmware_updater; mod mem_flash; mod partition;