| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| crate::{repo_keys::RepoKeys, repository::PmRepository}, |
| anyhow::{Context, Result}, |
| camino::{Utf8Path, Utf8PathBuf}, |
| fidl_fuchsia_pkg_ext::RepositoryKey, |
| fuchsia_hash::Hash, |
| fuchsia_pkg::{PackageBuilder, PackageManifest}, |
| fuchsia_url::RelativePackageUrl, |
| futures::io::AllowStdIo, |
| maplit::hashmap, |
| std::{ |
| collections::HashSet, |
| fs::{copy, create_dir, create_dir_all, File}, |
| path::{Path, PathBuf}, |
| }, |
| tempfile::TempDir, |
| tuf::{ |
| crypto::{Ed25519PrivateKey, HashAlgorithm}, |
| metadata::{Delegation, Delegations, MetadataDescription, MetadataPath, TargetPath}, |
| pouf::Pouf1, |
| repo_builder::RepoBuilder, |
| repository::FileSystemRepositoryBuilder, |
| }, |
| walkdir::WalkDir, |
| }; |
| |
| #[cfg(not(target_os = "fuchsia"))] |
| use crate::repo_client::RepoClient; |
| #[cfg(not(target_os = "fuchsia"))] |
| use crate::repository::{FileSystemRepository, RepoProvider}; |
| #[cfg(not(target_os = "fuchsia"))] |
| use anyhow::anyhow; |
| |
| const EMPTY_REPO_PATH: &str = concat!(env!("ROOT_OUT_DIR"), "/test_data/ffx_lib_pkg/empty-repo"); |
| |
| #[cfg(not(target_os = "fuchsia"))] |
| #[cfg(test)] |
| pub(crate) const PKG1_HASH: &str = |
| "2881455493b5870aaea36537d70a2adc635f516ac2092598f4b6056dabc6b25d"; |
| |
| #[cfg(not(target_os = "fuchsia"))] |
| #[cfg(test)] |
| pub(crate) const PKG2_HASH: &str = |
| "050907f009ff634f9aa57bff541fb9e9c2c62b587c23578e77637cda3bd69458"; |
| |
| #[cfg(not(target_os = "fuchsia"))] |
| #[cfg(test)] |
| pub(crate) const PKG1_BIN_HASH: &str = |
| "72e1e7a504f32edf4f23e7e8a3542c1d77d12541142261cfe272decfa75f542d"; |
| |
| #[cfg(not(target_os = "fuchsia"))] |
| #[cfg(test)] |
| pub(crate) const PKG1_LIB_HASH: &str = |
| "8a8a5f07f935a4e8e1fd1a1eda39da09bb2438ec0adfb149679ddd6e7e1fbb4f"; |
| |
| #[cfg(not(target_os = "fuchsia"))] |
| #[cfg(test)] |
| pub(crate) const PKG2_BIN_HASH: &str = |
| "548981eb310ddc4098fb5c63692e19ac4ae287b13d0e911fbd9f7819ac22491c"; |
| |
| #[cfg(not(target_os = "fuchsia"))] |
| #[cfg(test)] |
| pub(crate) const PKG2_LIB_HASH: &str = |
| "ecc11f7f4b763c5a21be2b4159c9818bbe22ca7e6d8100a72f6a41d3d7b827a9"; |
| |
| #[cfg(not(target_os = "fuchsia"))] |
| #[cfg(test)] |
| pub(crate) const ANONSUBPKG_HASH: &str = |
| "1d20cc5163f35cd977cf414d54f8d5b53bcb33944cff0b9560b0627a6bba7441"; |
| |
| #[cfg(not(target_os = "fuchsia"))] |
| #[cfg(test)] |
| pub(crate) const ANONSUBPKG_BIN_HASH: &str = |
| "7fb2c78b2ae5ce6b591c4b017b382e5d4f1dc2a6f3e6977cac735632bbffec1f"; |
| |
| #[cfg(not(target_os = "fuchsia"))] |
| #[cfg(test)] |
| pub(crate) const ANONSUBPKG_LIB_HASH: &str = |
| "8dda14b0bc837a825c71a8534e402b5c7d4dfbb7348f429c017e57d547be80df"; |
| |
| #[cfg(not(target_os = "fuchsia"))] |
| #[cfg(test)] |
| pub(crate) const NAMEDSUBPKG_HASH: &str = |
| "1e67965c0601afbe00e43eb5c50509813400f1ae0ec838b44dfa018a8afcdeac"; |
| |
| #[cfg(not(target_os = "fuchsia"))] |
| #[cfg(test)] |
| pub(crate) const NAMEDSUBPKG_BIN_HASH: &str = |
| "5c8e2b5f4f5be036f9694842767a226c776c89e21bf6705e89d03ab4543fea2e"; |
| |
| #[cfg(not(target_os = "fuchsia"))] |
| #[cfg(test)] |
| pub(crate) const NAMEDSUBPKG_LIB_HASH: &str = |
| "208995a7397ea03e5b567a966690336d3c2cbbbf03bea95a4ee95724ac42f2e6"; |
| |
| #[cfg(not(target_os = "fuchsia"))] |
| #[cfg(test)] |
| pub(crate) const SUPERPKG_HASH: &str = |
| "cb174413bb34d960976f6b82ef6e6d3d3a36ddccdec142faf3d79f76c4baa676"; |
| |
| #[cfg(not(target_os = "fuchsia"))] |
| #[cfg(test)] |
| pub(crate) const SUPERPKG_BIN_HASH: &str = |
| "5ece8a1e67d2f16b861ad282667760cda93684b5ab5c80c4be6b9be47395b2b1"; |
| |
| #[cfg(not(target_os = "fuchsia"))] |
| #[cfg(test)] |
| pub(crate) const SUPERPKG_LIB_HASH: &str = |
| "b54bb3adf0fc9492d5e12cce9cf0b3dc501075397e8eedb77991503139c1ad40"; |
| |
| pub fn repo_key() -> RepositoryKey { |
| RepositoryKey::Ed25519( |
| [ |
| 29, 76, 86, 76, 184, 70, 108, 73, 249, 127, 4, 47, 95, 63, 36, 35, 101, 255, 212, 33, |
| 10, 154, 26, 130, 117, 157, 125, 88, 175, 214, 109, 113, |
| ] |
| .to_vec(), |
| ) |
| } |
| |
| pub fn repo_private_key() -> Ed25519PrivateKey { |
| Ed25519PrivateKey::from_ed25519(&[ |
| 80, 121, 161, 145, 5, 165, 178, 98, 248, 146, 132, 195, 60, 32, 72, 122, 150, 223, 124, |
| 216, 217, 43, 74, 9, 221, 38, 156, 113, 181, 63, 234, 98, 190, 11, 152, 63, 115, 150, 218, |
| 103, 92, 64, 198, 185, 62, 71, 252, 237, 124, 30, 158, 168, 163, 42, 31, 233, 82, 186, 143, |
| 81, 151, 96, 179, 7, |
| ]) |
| .unwrap() |
| } |
| |
| fn copy_dir(from: &Path, to: &Path) -> Result<()> { |
| let walker = WalkDir::new(from); |
| for entry in walker.into_iter() { |
| let entry = entry?; |
| let to_path = to.join(entry.path().strip_prefix(from)?); |
| if entry.metadata()?.is_dir() { |
| if to_path.exists() { |
| continue; |
| } else { |
| create_dir_all(&to_path).with_context(|| format!("creating {to_path:?}"))?; |
| } |
| } else { |
| copy(entry.path(), &to_path) |
| .with_context(|| format!("copying {:?} to {:?}", entry.path(), to_path))?; |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| pub fn make_repo_keys() -> RepoKeys { |
| let keys_dir = Utf8PathBuf::from(EMPTY_REPO_PATH).join("keys"); |
| let repo_keys = RepoKeys::from_dir(keys_dir.as_std_path()).unwrap(); |
| |
| assert_eq!(repo_keys.root_keys().len(), 1); |
| assert_eq!(repo_keys.targets_keys().len(), 1); |
| assert_eq!(repo_keys.snapshot_keys().len(), 1); |
| assert_eq!(repo_keys.timestamp_keys().len(), 1); |
| |
| repo_keys |
| } |
| |
| pub fn make_repo_keys_dir(root: &Utf8Path) { |
| let src = PathBuf::from(EMPTY_REPO_PATH).canonicalize().unwrap(); |
| copy_dir(&src.join("keys"), root.as_std_path()).unwrap(); |
| } |
| |
| pub fn make_empty_pm_repo_dir(root: &Utf8Path) { |
| let src = PathBuf::from(EMPTY_REPO_PATH).canonicalize().unwrap(); |
| copy_dir(&src, root.as_std_path()).unwrap(); |
| } |
| |
| #[cfg(not(target_os = "fuchsia"))] |
| pub async fn make_readonly_empty_repository() -> Result<RepoClient<Box<dyn RepoProvider>>> { |
| let backend = PmRepository::new(Utf8PathBuf::from(EMPTY_REPO_PATH)); |
| let mut client = RepoClient::from_trusted_remote(Box::new(backend) as Box<_>) |
| .await |
| .map_err(|e| anyhow!(e))?; |
| client.update().await?; |
| Ok(client) |
| } |
| |
| #[cfg(not(target_os = "fuchsia"))] |
| pub async fn make_writable_empty_repository( |
| root: Utf8PathBuf, |
| ) -> Result<RepoClient<Box<dyn RepoProvider>>> { |
| make_empty_pm_repo_dir(&root); |
| let backend = PmRepository::new(root); |
| let mut client = RepoClient::from_trusted_remote(Box::new(backend) as Box<_>).await?; |
| client.update().await?; |
| Ok(client) |
| } |
| |
| pub fn make_package_manifest( |
| name: &str, |
| build_path: &Path, |
| subpackages: Vec<(RelativePackageUrl, Hash, PathBuf)>, |
| ) -> (PathBuf, PackageManifest) { |
| make_package_manifest_with_abi_revision( |
| name, |
| build_path, |
| subpackages, |
| // ABI revision for API level 7 |
| 0xECCEA2F70ACD6FC0.into(), |
| ) |
| } |
| |
| pub fn make_package_manifest_with_abi_revision( |
| name: &str, |
| build_path: &Path, |
| subpackages: Vec<(RelativePackageUrl, Hash, PathBuf)>, |
| abi_revision: version_history::AbiRevision, |
| ) -> (PathBuf, PackageManifest) { |
| let package_path = build_path.join(name); |
| |
| let mut builder = PackageBuilder::new(name, abi_revision); |
| |
| builder |
| .add_contents_as_blob( |
| format!("bin/{name}"), |
| format!("binary {name}").as_bytes(), |
| &package_path, |
| ) |
| .unwrap(); |
| builder |
| .add_contents_as_blob( |
| format!("lib/{name}"), |
| format!("lib {name}").as_bytes(), |
| &package_path, |
| ) |
| .unwrap(); |
| builder |
| .add_contents_to_far( |
| format!("meta/{name}.cm"), |
| format!("cm {name}").as_bytes(), |
| &package_path, |
| ) |
| .unwrap(); |
| builder |
| .add_contents_to_far( |
| format!("meta/{name}.cmx"), |
| format!("cmx {name}").as_bytes(), |
| &package_path, |
| ) |
| .unwrap(); |
| |
| for (name, hash, manifest_path) in subpackages { |
| builder.add_subpackage(&name, hash, manifest_path).unwrap(); |
| } |
| |
| let meta_far_path = package_path.join("meta.far"); |
| let manifest = builder.build(&package_path, &meta_far_path).unwrap(); |
| |
| (meta_far_path, manifest) |
| } |
| |
| pub async fn make_package_archive(archive_name: &str, outdir: &Path) -> Utf8PathBuf { |
| let build_path = TempDir::new().unwrap(); |
| let package_path = build_path.path().join(archive_name); |
| let (_, manifest) = make_package_manifest(archive_name, build_path.path(), Vec::new()); |
| let far_file = format!("{archive_name}.far"); |
| |
| let archive_path = outdir.join(&far_file); |
| let archive_file = File::create(archive_path.clone()).unwrap(); |
| manifest.archive(&package_path, &archive_file).await.unwrap(); |
| Utf8PathBuf::from_path_buf(archive_path).expect("convert archive pathbuf to utf8pathbuf") |
| } |
| |
| pub async fn make_pm_repo_dir(repo_dir: &Path) { |
| let keys_dir = repo_dir.join("keys"); |
| create_dir_all(&keys_dir).unwrap(); |
| copy_dir(Utf8PathBuf::from(EMPTY_REPO_PATH).join("keys").as_std_path(), &keys_dir).unwrap(); |
| |
| let metadata_dir = repo_dir.join("repository"); |
| let blobs_dir = metadata_dir.join("blobs"); |
| make_repo_dir(&metadata_dir, &blobs_dir).await; |
| } |
| |
| pub async fn make_repo_dir(metadata_dir: &Path, blobs_dir: &Path) { |
| create_dir_all(metadata_dir).unwrap(); |
| create_dir_all(blobs_dir).unwrap(); |
| |
| // Construct some packages for the repository. |
| let build_tmp = tempfile::tempdir().unwrap(); |
| let build_path = build_tmp.path(); |
| |
| // Write TUF metadata |
| let repo = |
| FileSystemRepositoryBuilder::<Pouf1>::new(metadata_dir).targets_prefix("targets").build(); |
| |
| let repo_keys = make_repo_keys(); |
| let root_keys = repo_keys.root_keys().iter().map(|k| &**k).collect::<Vec<_>>(); |
| let targets_keys = repo_keys.targets_keys().iter().map(|k| &**k).collect::<Vec<_>>(); |
| let snapshot_keys = repo_keys.snapshot_keys().iter().map(|k| &**k).collect::<Vec<_>>(); |
| let timestamp_keys = repo_keys.timestamp_keys().iter().map(|k| &**k).collect::<Vec<_>>(); |
| |
| let mut builder = RepoBuilder::create(repo) |
| .trusted_root_keys(&root_keys) |
| .trusted_targets_keys(&targets_keys) |
| .trusted_snapshot_keys(&snapshot_keys) |
| .trusted_timestamp_keys(×tamp_keys) |
| .stage_root() |
| .unwrap(); |
| |
| // Add all the packages to the metadata. |
| for name in ["package1", "package2"] { |
| let (meta_far_path, manifest) = make_package_manifest(name, build_path, Vec::new()); |
| |
| // Copy the package blobs into the blobs directory. |
| let mut meta_far_merkle = None; |
| for blob in manifest.blobs() { |
| let merkle = blob.merkle.to_string(); |
| |
| if blob.path == "meta/" { |
| meta_far_merkle = Some(merkle.clone()); |
| } |
| |
| let mut src = std::fs::File::open(&blob.source_path).unwrap(); |
| let mut dst = std::fs::File::create(blobs_dir.join(&merkle)).unwrap(); |
| std::io::copy(&mut src, &mut dst).unwrap(); |
| |
| let blob_type = delivery_blob::DeliveryBlobType::Type1; |
| crate::repository::file_system::generate_delivery_blob( |
| blob.source_path.as_str().into(), |
| &Utf8PathBuf::from_path_buf( |
| blobs_dir.join(format!("{}/{merkle}", u32::from(blob_type))), |
| ) |
| .unwrap(), |
| blob_type, |
| ) |
| .await |
| .unwrap(); |
| } |
| builder = builder |
| .add_target_with_custom( |
| TargetPath::new(format!("{name}/0")).unwrap(), |
| AllowStdIo::new(File::open(meta_far_path).unwrap()), |
| hashmap! { "merkle".into() => meta_far_merkle.unwrap().into() }, |
| ) |
| .await |
| .unwrap(); |
| } |
| |
| // Even though we don't use delegations, add a simple one to make sure we at least preserve them |
| // when we modify repositories. |
| let delegations_keys = targets_keys.clone(); |
| let delegations = Delegations::new( |
| delegations_keys |
| .iter() |
| .map(|k| (k.public().key_id().clone(), k.public().clone())) |
| .collect(), |
| vec![Delegation::new( |
| MetadataPath::new("delegation").unwrap(), |
| false, |
| 1, |
| delegations_keys.iter().map(|k| k.public().key_id().clone()).collect(), |
| HashSet::from([TargetPath::new("some-delegated-target").unwrap()]), |
| ) |
| .unwrap()], |
| ) |
| .unwrap(); |
| |
| builder |
| .stage_targets_with_builder(|b| b.delegations(delegations)) |
| .unwrap() |
| .stage_snapshot_with_builder(|b| { |
| b.insert_metadata_description( |
| MetadataPath::new("delegation").unwrap(), |
| MetadataDescription::from_slice(&[0u8], 1, &[HashAlgorithm::Sha256]).unwrap(), |
| ) |
| }) |
| .unwrap() |
| .commit() |
| .await |
| .unwrap(); |
| } |
| |
| pub async fn make_pm_repository(dir: impl Into<Utf8PathBuf>) -> PmRepository { |
| let dir = dir.into(); |
| let metadata_dir = dir.join("repository"); |
| let blobs_dir = metadata_dir.join("blobs"); |
| make_repo_dir(metadata_dir.as_std_path(), blobs_dir.as_std_path()).await; |
| |
| let keys_dir = dir.join("keys"); |
| create_dir(&keys_dir).unwrap(); |
| |
| let empty_repo_dir = PathBuf::from(EMPTY_REPO_PATH).canonicalize().unwrap(); |
| copy_dir(&empty_repo_dir.join("keys"), keys_dir.as_std_path()).unwrap(); |
| |
| PmRepository::new(dir) |
| } |
| |
| #[cfg(not(target_os = "fuchsia"))] |
| pub async fn make_file_system_repository( |
| metadata_dir: impl Into<Utf8PathBuf>, |
| blobs_dir: impl Into<Utf8PathBuf>, |
| ) -> RepoClient<Box<dyn RepoProvider>> { |
| let metadata_dir = metadata_dir.into(); |
| let blobs_dir = blobs_dir.into(); |
| make_repo_dir(metadata_dir.as_std_path(), blobs_dir.as_std_path()).await; |
| |
| let backend = FileSystemRepository::new(metadata_dir, blobs_dir); |
| let mut client = RepoClient::from_trusted_remote(Box::new(backend) as Box<_>).await.unwrap(); |
| client.update().await.unwrap(); |
| client |
| } |