Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions kinode/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ async fn main() -> anyhow::Result<()> {
let mut bootstrapped_processes = Vec::new();
writeln!(
bootstrapped_processes,
"pub static BOOTSTRAPPED_PROCESSES: &[(&str, &[u8])] = &[",
"pub static BOOTSTRAPPED_PROCESSES: &[(&str, &[u8], &[u8])] = &[",
)
.unwrap();
let packages_dir = format!("{}/packages", pwd.display());
eprintln!("{packages_dir:?}");
for entry in std::fs::read_dir(packages_dir).unwrap() {
let entry_path = entry.unwrap().path();
let metadata_path = format!("{}/metadata.json", entry_path.display());
let parent_pkg_path = format!("{}/pkg", entry_path.display());

kit::build::execute(&entry_path, false, false, false, true).await?;
Expand Down Expand Up @@ -72,8 +73,8 @@ async fn main() -> anyhow::Result<()> {
// Add zip bytes to bootstrapped_processes.rs
writeln!(
bootstrapped_processes,
" (\"{}\", include_bytes!(\"{}\")),",
zip_filename, zip_path,
" (\"{}\", include_bytes!(\"{}\"), include_bytes!(\"{}\")),",
zip_filename, metadata_path, zip_path,
)
.unwrap();
}
Expand Down
4 changes: 3 additions & 1 deletion kinode/packages/app_store/app_store/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy.git", rev = "3b1c31
alloy-sol-types = "0.5.1"
anyhow = "1.0"
bincode = "1.3.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.5.9-alpha", features = ["eth"] }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", rev = "7f409e4", features = [
"eth",
] }
rand = "0.8"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Expand Down
11 changes: 6 additions & 5 deletions kinode/packages/app_store/app_store/src/http_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub fn handle_http_request(
req: &IncomingHttpRequest,
) -> anyhow::Result<()> {
match serve_paths(our, state, requested_packages, req) {
Ok((status_code, headers, body)) => send_response(
Ok((status_code, _headers, body)) => send_response(
status_code,
Some(HashMap::from([(
String::from("Content-Type"),
Expand Down Expand Up @@ -81,6 +81,7 @@ fn gen_package_info(
"caps_approved": state.caps_approved,
"mirroring": state.mirroring,
"auto_update": state.auto_update,
"verified": state.verified,
}),
None => json!(null),
},
Expand Down Expand Up @@ -188,7 +189,7 @@ fn serve_paths(
}
Method::PUT => {
// update an app
let pkg_listing: &PackageListing = state
let _pkg_listing: &PackageListing = state
.get_listing(&package_id)
.ok_or(anyhow::anyhow!("No package"))?;
let pkg_state: &PackageState = state
Expand Down Expand Up @@ -277,10 +278,10 @@ fn serve_paths(
let mirrors: &Vec<NodeId> = pkg_listing
.metadata
.as_ref()
.ok_or(anyhow::anyhow!("No metadata for package {package_id}"))?
.expect("Package does not have metadata")
.properties
.mirrors
.as_ref()
.ok_or(anyhow::anyhow!("No mirrors for package {package_id}"))?;
.as_ref();
let download_from = body_json
.get("download_from")
.unwrap_or(&json!(mirrors
Expand Down
49 changes: 36 additions & 13 deletions kinode/packages/app_store/app_store/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ fn handle_local_request(
mirrored_from: Some(our.node.clone()),
our_version,
installed: false,
verified: true, // side loaded apps are implicitly verified because there is no "source" to verify against
caps_approved: true, // TODO see if we want to auto-approve local installs
manifest_hash: None, // generated in the add fn
mirroring: *mirror,
Expand Down Expand Up @@ -404,35 +405,56 @@ fn handle_receive_download(
// check the version hash for this download against requested!!
// for now we can reject if it's not latest.
let download_hash = generate_version_hash(&blob.bytes);
let mut verified = false;
match requested_package.desired_version_hash {
Some(hash) => {
if download_hash != hash {
return Err(anyhow::anyhow!(
"app store: downloaded package is not desired version--rejecting download! download hash: {download_hash}, desired hash: {hash}"
));
if hash.is_empty() {
println!(
"\x1b[33mwarning: downloaded package has no version hashes--cannot verify code integrity, proceeding anyways\x1b[0m"
);
} else {
return Err(anyhow::anyhow!(
"app store: downloaded package is not desired version--rejecting download! download hash: {download_hash}, desired hash: {hash}"
));
}
} else {
verified = true;
}
}
None => {
// check against latest from listing
// check against `metadata.properties.current_version`
let Some(package_listing) = state.get_listing(&package_id) else {
return Err(anyhow::anyhow!(
"app store: downloaded package cannot be found in manager--rejecting download!"
));
};
if let Some(metadata) = &package_listing.metadata {
if let Some(latest_hash) = metadata.versions.clone().unwrap_or(vec![]).last() {
if &download_hash != latest_hash {
return Err(anyhow::anyhow!(
"app store: downloaded package is not latest version--rejecting download! download hash: {download_hash}, latest hash: {latest_hash}"
));
}
let Some(metadata) = &package_listing.metadata else {
return Err(anyhow::anyhow!(
"app store: downloaded package has no metadata to check validity against!"
));
};
let Some(latest_hash) = metadata
.properties
.code_hashes
.get(&metadata.properties.current_version)
else {
return Err(anyhow::anyhow!(
"app store: downloaded package has no versions in manager--rejecting download!"
));
};
if &download_hash != latest_hash {
if latest_hash.is_empty() {
println!(
"\x1b[33mwarning: downloaded package has no version hashes--cannot verify code integrity, proceeding anyways\x1b[0m"
);
} else {
return Err(anyhow::anyhow!(
"app store: downloaded package has no versions in manager--rejecting download!"
"app store: downloaded package is not latest version--rejecting download! download hash: {download_hash}, latest hash: {latest_hash}"
));
}
} else {
println!("app store: warning: downloaded package has no listing metadata to check validity against!")
verified = true;
}
}
}
Expand All @@ -451,6 +473,7 @@ fn handle_receive_download(
mirrored_from: Some(requested_package.from),
our_version: download_hash,
installed: false,
verified,
caps_approved: false,
manifest_hash: None, // generated in the add fn
mirroring: requested_package.mirror,
Expand Down
49 changes: 24 additions & 25 deletions kinode/packages/app_store/app_store/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,7 @@ pub struct PackageListing {
pub name: String,
pub publisher: NodeId,
pub metadata_hash: String,
pub metadata: Option<OnchainPackageMetadata>,
}

/// metadata derived from metadata hash in listing event
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OnchainPackageMetadata {
pub name: Option<String>,
pub subtitle: Option<String>,
pub description: Option<String>,
pub image: Option<String>,
pub version: Option<String>,
pub license: Option<String>,
pub website: Option<String>,
pub screenshots: Option<Vec<String>>,
pub mirrors: Option<Vec<NodeId>>,
pub versions: Option<Vec<String>>,
pub metadata: Option<kt::Erc721Metadata>,
}

#[derive(Debug, Serialize, Deserialize)]
Expand All @@ -86,13 +71,14 @@ pub struct PackageState {
/// the version of the package we have downloaded
pub our_version: String,
pub installed: bool,
pub verified: bool,
pub caps_approved: bool,
pub manifest_hash: Option<String>,
/// are we serving this package to others?
pub mirroring: bool,
/// if we get a listing data update, will we try to download it?
pub auto_update: bool,
pub metadata: Option<OnchainPackageMetadata>,
pub metadata: Option<kt::Erc721Metadata>,
}

/// this process's saved state
Expand Down Expand Up @@ -318,6 +304,7 @@ impl State {
mirrored_from: None,
our_version,
installed: true,
verified: true, // implicity verified
caps_approved: true, // since it's already installed this must be true
manifest_hash: Some(generate_metadata_hash(&manifest_bytes)),
mirroring: false,
Expand Down Expand Up @@ -421,6 +408,15 @@ impl State {

let metadata = fetch_metadata(&metadata_url, &metadata_hash).ok();

if let Some(metadata) = &metadata {
if metadata.properties.publisher != publisher_name {
return Err(anyhow::anyhow!(format!(
"app store: metadata publisher name mismatch: got {}, expected {}",
metadata.properties.publisher, publisher_name
)));
}
}

let listing = match self.get_listing_with_hash_mut(&package_hash) {
Some(current_listing) => {
current_listing.name = package_name;
Expand Down Expand Up @@ -460,7 +456,15 @@ impl State {
))?;

let metadata = match fetch_metadata(&metadata_url, &metadata_hash) {
Ok(metadata) => Some(metadata),
Ok(metadata) => {
if metadata.properties.publisher != current_listing.publisher {
return Err(anyhow::anyhow!(format!(
"app store: metadata publisher name mismatch: got {}, expected {}",
metadata.properties.publisher, current_listing.publisher
)));
}
Some(metadata)
}
Err(e) => {
crate::print_to_terminal(
1,
Expand Down Expand Up @@ -583,20 +587,15 @@ fn dnswire_decode(wire_format_bytes: &[u8]) -> Result<String, std::string::FromU
}

/// fetch metadata from metadata_url and verify it matches metadata_hash
fn fetch_metadata(
metadata_url: &str,
metadata_hash: &str,
) -> anyhow::Result<OnchainPackageMetadata> {
fn fetch_metadata(metadata_url: &str, metadata_hash: &str) -> anyhow::Result<kt::Erc721Metadata> {
let url = url::Url::parse(metadata_url)?;
let _response = http::send_request_await_response(http::Method::GET, url, None, 5, vec![])?;
let Some(body) = get_blob() else {
return Err(anyhow::anyhow!("no blob"));
};
let hash = generate_metadata_hash(&body.bytes);
if &hash == metadata_hash {
Ok(serde_json::from_slice::<OnchainPackageMetadata>(
&body.bytes,
)?)
Ok(serde_json::from_slice::<kt::Erc721Metadata>(&body.bytes)?)
} else {
Err(anyhow::anyhow!(
"metadata hash mismatch: got {hash}, expected {metadata_hash}"
Expand Down
16 changes: 16 additions & 0 deletions kinode/packages/app_store/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "App Store",
"description": "A package manager + app store.",
"image": "",
"properties": {
"package_name": "app_store",
"current_version": "0.3.0",
"publisher": "sys",
"mirrors": [],
"code_hashes": {
"0.3.0": ""
}
},
"external_url": "https://kinode.org",
"animation_url": ""
}
10 changes: 0 additions & 10 deletions kinode/packages/app_store/pkg/metadata.json

This file was deleted.

Loading