diff --git a/lib/src/edit/mod.rs b/lib/src/edit/mod.rs index f66d762..ee61487 100644 --- a/lib/src/edit/mod.rs +++ b/lib/src/edit/mod.rs @@ -21,7 +21,5 @@ pub use key::*; pub use root::*; pub use sign::*; -const KIB: u64 = 1024; -const MIB: u64 = 1024 * KIB; #[expect(clippy::unreadable_literal)] const OXIDE_BOOT_MAGIC: [u8; 4] = 0x1DEB0075_u32.to_le_bytes(); diff --git a/lib/src/edit/os_images.rs b/lib/src/edit/os_images.rs index 4c3a01c..6483e7a 100644 --- a/lib/src/edit/os_images.rs +++ b/lib/src/edit/os_images.rs @@ -11,6 +11,8 @@ use camino::Utf8Path; use camino::Utf8PathBuf; use flate2::read::GzDecoder; use futures_util::TryStreamExt; +use sha2::Digest; +use sha2::Sha256; use tokio::fs::File; use tokio::io::AsyncReadExt; use tufaceous_artifact::ArtifactVersion; @@ -19,8 +21,6 @@ use tufaceous_artifact::OsVariant; use crate::COSMO_PHASE_1_PATH; use crate::GIMLET_PHASE_1_PATH; use crate::PHASE_2_PATH; -use crate::edit::KIB; -use crate::edit::MIB; use crate::edit::OXIDE_BOOT_MAGIC; use crate::edit::input::Input; use crate::edit::source::BytesSource; @@ -30,6 +30,9 @@ use crate::error::Error; use crate::error::ErrorKind; use crate::error::try_path; +const KIB: u64 = 1024; +const MIB: u64 = 1024 * KIB; + impl Input> { pub(crate) async fn os_images( os_variant: OsVariant, @@ -210,16 +213,28 @@ impl Input { format!( "cosmo {os_variant} OS phase 1 image version {interior_version}\n" ), - MIB, + 32 * KIB, ); let gimlet_phase_1 = BytesSource::fake_padded( format!( "gimlet {os_variant} OS phase 1 image version {interior_version}\n" ), - MIB, + 16 * KIB, ); - let mut phase_2_bytes = BytesMut::with_capacity(4096); + let phase_2_size = 64 * KIB; + let phase_2_data = + format!("OS phase 2 image version {interior_version}\n"); + let mut hasher = Sha256::new(); + hasher.update(&phase_2_data); + hasher.update(vec![ + 0; + usize::try_from(phase_2_size).unwrap() + - phase_2_data.len() + ]); + + let mut phase_2_bytes = + BytesMut::with_capacity(4096 + phase_2_data.len()); phase_2_bytes.put(OXIDE_BOOT_MAGIC.as_slice()); // uint32_t odh_magic; phase_2_bytes.put_u32_le(2); // uint32_t odh_version; // The only defined ODH_FLAG is: @@ -233,12 +248,7 @@ impl Input { phase_2_bytes.put_u64_le(1 << 32); // uint64_t odh_target_size; // #define OXBOOT_CSUMLEN_SHA256 32 // uint8_t odh_sha256[OXBOOT_CSUMLEN_SHA256]; - phase_2_bytes.put( - // head -c $((4 * 1024 * 1024)) { OsVariant::Recovery => "recovery".as_bytes(), }); phase_2_bytes.put(" fake123/789fake 1986-12-28 01:23".as_bytes()); - // rest of header is zeroes, which will be written out by `fake_padded` - let phase_2 = BytesSource::fake_padded(phase_2_bytes, 4096 + 4 * MIB); + phase_2_bytes.resize(4096, 0); // rest of header is zeroes + phase_2_bytes.put(phase_2_data.as_bytes()); + let phase_2 = + BytesSource::fake_padded(phase_2_bytes, 4096 + phase_2_size); let mut extra_targets = BTreeMap::new(); extra_targets.insert( String::from("unix.z"), BytesSource::fake_padded( format!("{os_variant} OS unix.z version {interior_version}\n"), - 64 * KIB, + 32 * KIB, ), ); extra_targets.insert( String::from("cpio.z"), BytesSource::fake_padded( format!("{os_variant} OS cpio.z version {interior_version}\n"), - 256 * KIB, + 32 * KIB, ), ); diff --git a/lib/tests/tests.rs b/lib/tests/tests.rs index 4f1f5e6..a52702b 100644 --- a/lib/tests/tests.rs +++ b/lib/tests/tests.rs @@ -2,6 +2,8 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +use std::collections::BTreeMap; +use std::collections::BTreeSet; use std::convert::Infallible; use std::io::Write; use std::sync::Arc; @@ -15,6 +17,8 @@ use semver::Version; use tufaceous::RepositoryLoader; use tufaceous::edit::RepositoryEditor; use tufaceous::error::Error; +use tufaceous_artifact::Artifact; +use tufaceous_artifact::ArtifactHash; use tufaceous_artifact::KnownArtifactTags; const V1: Version = Version::new(1, 0, 0); @@ -98,6 +102,65 @@ async fn no_artifacts() -> Result<(), Error> { Ok(()) } +#[tokio::test] +async fn fake_artifacts_are_distinct() -> Result<(), Error> { + let log = slog::Logger::root(slog::Discard, slog::o!()); + + let signed = RepositoryEditor::fake(V1)? + .finish() + .await? + .generate_root() + .sign() + .await?; + let zip = signed.write_zip(Vec::new(), Utc::now()).await?; + let repo1 = RepositoryLoader::new() + .trust_root(signed.root()) + .load_zip_buffer(zip, &log) + .await?; + + let signed = RepositoryEditor::fake(V2)? + .finish() + .await? + .generate_root() + .sign() + .await?; + let zip = signed.write_zip(Vec::new(), Utc::now()).await?; + let repo2 = RepositoryLoader::new() + .trust_root(signed.root()) + .load_zip_buffer(zip, &log) + .await?; + + let hashes1 = repo1 + .artifacts() + .iter() + .map(|artifact| artifact.hash) + .collect::>(); + let hashes2 = repo2 + .artifacts() + .iter() + .map(|artifact| artifact.hash) + .collect::>(); + if !hashes1.is_disjoint(&hashes2) { + let intersection = + hashes1.intersection(&hashes2).collect::>(); + let mut artifacts = BTreeMap::>::new(); + for repo in [&repo1, &repo2] { + for artifact in repo.artifacts() { + if intersection.contains(&artifact.hash) { + artifacts.entry(artifact.hash).or_default().push(artifact); + } + } + } + panic!( + "artifacts from fake repos of different versions \ + have the same hash: {:?}", + artifacts.values() + ); + } + + Ok(()) +} + #[tokio::test] async fn inconsistent_fake() -> Result<(), Error> { let log = slog::Logger::root(slog::Discard, slog::o!());