From d250255f66a54014a4900503e9c7b5c76a432469 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 9 Oct 2023 12:24:26 -0700 Subject: [PATCH] Added minisign. --- Cargo.lock | 7 ++++ crates/core/Cargo.toml | 1 + crates/core/src/error.rs | 13 ++++++ crates/core/src/tool.rs | 84 +++++++++++++++++++++++++++------------ crates/pdk-api/src/api.rs | 7 ++-- 5 files changed, 84 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dc739dedb..4082e287c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2035,6 +2035,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "minisign-verify" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "933dca44d65cdd53b355d0b73d380a2ff5da71f87f036053188bf1eab6a19881" + [[package]] name = "miniz_oxide" version = "0.6.2" @@ -2332,6 +2338,7 @@ dependencies = [ "extism", "human-sort", "miette", + "minisign-verify", "once_cell", "proto_pdk_api", "proto_wasm_plugin", diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 6967c8996..7093723a8 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -16,6 +16,7 @@ cached = { workspace = true } extism = { workspace = true } human-sort = { workspace = true } miette = { workspace = true } +minisign-verify = "0.2.1" once_cell = { workspace = true } regex = { workspace = true } reqwest = { workspace = true } diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 24fac8473..6d34ae2ef 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -25,6 +25,12 @@ pub enum ProtoError { #[error("Internet connection required, unable to download and install tools.")] InternetConnectionRequired, + #[diagnostic(code(proto::verify::missing_public_key))] + #[error( + "A {} is required when using minisig.", "checksum_public_key".style(Style::Property) + )] + MissingChecksumPublicKey, + #[diagnostic(code(proto::verify::invalid_checksum))] #[error( "Checksum has failed for {}, which was verified using {}.", .download.style(Style::Path), .checksum.style(Style::Path) @@ -91,6 +97,13 @@ pub enum ProtoError { error: reqwest::Error, }, + #[diagnostic(code(proto::verify::minisign))] + #[error("Failed to verify minisign checksum.")] + Minisign { + #[source] + error: minisign_verify::Error, + }, + #[diagnostic(code(proto::version::invalid))] #[error("Invalid version or requirement {}.", .version.style(Style::Hash))] Semver { diff --git a/crates/core/src/tool.rs b/crates/core/src/tool.rs index 39dfc80e4..82f91f6f2 100644 --- a/crates/core/src/tool.rs +++ b/crates/core/src/tool.rs @@ -626,6 +626,7 @@ impl Tool { &self, checksum_file: &Path, download_file: &Path, + checksum_public_key: Option<&str>, ) -> miette::Result { debug!( tool = self.id.as_str(), @@ -634,44 +635,69 @@ impl Tool { "Verifiying checksum of downloaded file", ); - let checksum = hash_file_contents(download_file)?; + let mut verified = false; // Allow plugin to provide their own checksum verification method if self.plugin.has_func("verify_checksum") { let result: VerifyChecksumOutput = self.plugin.call_func_with( "verify_checksum", VerifyChecksumInput { - checksum, checksum_file: self.to_virtual_path(checksum_file), download_file: self.to_virtual_path(download_file), context: self.create_context()?, }, )?; - if result.verified { - return Ok(true); - } + verified = result.verified; // Otherwise attempt to verify it ourselves } else { - let file = fs::open_file(checksum_file)?; - let file_name = fs::file_name(download_file); - - for line in BufReader::new(file).lines().flatten() { - if - // - line.starts_with(&checksum) && line.ends_with(&file_name) || - // - line == checksum - { - debug!( - tool = self.id.as_str(), - "Successfully verified, checksum matches" - ); - - return Ok(true); + match checksum_file.extension().map(|e| e.to_str().unwrap()) { + Some("minisig" | "minisign") => { + use minisign_verify::*; + + let handle_error = |error: Error| ProtoError::Minisign { error }; + + PublicKey::from_base64( + checksum_public_key.ok_or_else(|| ProtoError::MissingChecksumPublicKey)?, + ) + .map_err(handle_error)? + .verify( + &fs::read_file_bytes(download_file)?, + &Signature::decode(&fs::read_file(checksum_file)?).map_err(handle_error)?, + false, + ) + .map_err(handle_error)?; + + verified = true; } - } + _ => { + let checksum_hash = hash_file_contents(download_file)?; + let checksum_matching_line = + format!("{} {}", checksum_hash, fs::file_name(download_file)); + + for line in BufReader::new(fs::open_file(checksum_file)?) + .lines() + .flatten() + { + // + // + if line == checksum_matching_line || line == checksum_hash { + verified = true; + break; + } + } + } + }; + } + + if verified { + debug!( + tool = self.id.as_str(), + "Successfully verified, checksum matches" + ); + + return Ok(true); } Err(ProtoError::InvalidChecksum { @@ -825,8 +851,10 @@ impl Tool { // Verify the checksum if applicable if let Some(checksum_url) = options.checksum_url { - let checksum_file = - temp_dir.join(options.checksum_name.unwrap_or("CHECKSUM.txt".to_owned())); + let checksum_file = temp_dir.join(match options.checksum_name { + Some(name) => name, + None => extract_filename_from_url(&checksum_url)?, + }); if !checksum_file.exists() { debug!( @@ -837,7 +865,13 @@ impl Tool { download_from_url_to_file(&checksum_url, &checksum_file, client).await?; } - self.verify_checksum(&checksum_file, &download_file).await?; + self.verify_checksum( + &checksum_file, + &download_file, + options.checksum_public_key.as_deref(), + ) + .await?; + fs::remove_file(checksum_file)?; } diff --git a/crates/pdk-api/src/api.rs b/crates/pdk-api/src/api.rs index 012b1669f..99d37c3ca 100644 --- a/crates/pdk-api/src/api.rs +++ b/crates/pdk-api/src/api.rs @@ -265,6 +265,10 @@ json_struct!( #[serde(skip_serializing_if = "Option::is_none")] pub checksum_name: Option, + /// Public key to use for checksum verification. + #[serde(skip_serializing_if = "Option::is_none")] + pub checksum_public_key: Option, + /// A secure URL to download the checksum file for verification. /// If the tool does not support checksum verification, this setting can be omitted. #[serde(skip_serializing_if = "Option::is_none")] @@ -300,9 +304,6 @@ json_struct!( /// Current tool context. pub context: ToolContext, - /// The SHA-256 hash of the downloaded file. - pub checksum: String, - /// Virtual path to the checksum file. pub checksum_file: VirtualPath,