|
@@ -3,18 +3,45 @@
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
-use crate::{
|
|
|
- bundle::{common::CommandExt, windows::util},
|
|
|
- Settings,
|
|
|
-};
|
|
|
-use std::{
|
|
|
- path::{Path, PathBuf},
|
|
|
- process::Command,
|
|
|
-};
|
|
|
-use winreg::{
|
|
|
- enums::{HKEY_LOCAL_MACHINE, KEY_READ, KEY_WOW64_32KEY},
|
|
|
- RegKey,
|
|
|
-};
|
|
|
+#[cfg(windows)]
|
|
|
+use crate::bundle::windows::util;
|
|
|
+use crate::{bundle::common::CommandExt, Settings};
|
|
|
+use anyhow::Context;
|
|
|
+#[cfg(windows)]
|
|
|
+use std::path::PathBuf;
|
|
|
+#[cfg(windows)]
|
|
|
+use std::sync::OnceLock;
|
|
|
+use std::{path::Path, process::Command};
|
|
|
+
|
|
|
+impl Settings {
|
|
|
+ pub(crate) fn can_sign(&self) -> bool {
|
|
|
+ self.windows().sign_command.is_some() || self.windows().certificate_thumbprint.is_some()
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn sign_params(&self) -> SignParams {
|
|
|
+ SignParams {
|
|
|
+ product_name: self.product_name().into(),
|
|
|
+ digest_algorithm: self
|
|
|
+ .windows()
|
|
|
+ .digest_algorithm
|
|
|
+ .as_ref()
|
|
|
+ .map(|algorithm| algorithm.to_string())
|
|
|
+ .unwrap_or_else(|| "sha256".to_string()),
|
|
|
+ certificate_thumbprint: self
|
|
|
+ .windows()
|
|
|
+ .certificate_thumbprint
|
|
|
+ .clone()
|
|
|
+ .unwrap_or_default(),
|
|
|
+ timestamp_url: self
|
|
|
+ .windows()
|
|
|
+ .timestamp_url
|
|
|
+ .as_ref()
|
|
|
+ .map(|url| url.to_string()),
|
|
|
+ tsp: self.windows().tsp,
|
|
|
+ sign_command: self.windows().sign_command.clone(),
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
pub struct SignParams {
|
|
|
pub product_name: String,
|
|
@@ -22,76 +49,79 @@ pub struct SignParams {
|
|
|
pub certificate_thumbprint: String,
|
|
|
pub timestamp_url: Option<String>,
|
|
|
pub tsp: bool,
|
|
|
+ pub sign_command: Option<String>,
|
|
|
}
|
|
|
|
|
|
-// sign code forked from https://github.com/forbjok/rust-codesign
|
|
|
-fn locate_signtool() -> crate::Result<PathBuf> {
|
|
|
- const INSTALLED_ROOTS_REGKEY_PATH: &str = r"SOFTWARE\Microsoft\Windows Kits\Installed Roots";
|
|
|
- const KITS_ROOT_REGVALUE_NAME: &str = r"KitsRoot10";
|
|
|
-
|
|
|
- let installed_roots_key_path = Path::new(INSTALLED_ROOTS_REGKEY_PATH);
|
|
|
-
|
|
|
- // Open 32-bit HKLM "Installed Roots" key
|
|
|
- let installed_roots_key = RegKey::predef(HKEY_LOCAL_MACHINE)
|
|
|
- .open_subkey_with_flags(installed_roots_key_path, KEY_READ | KEY_WOW64_32KEY)
|
|
|
- .map_err(|_| crate::Error::OpenRegistry(INSTALLED_ROOTS_REGKEY_PATH.to_string()))?;
|
|
|
-
|
|
|
- // Get the Windows SDK root path
|
|
|
- let kits_root_10_path: String = installed_roots_key
|
|
|
- .get_value(KITS_ROOT_REGVALUE_NAME)
|
|
|
- .map_err(|_| crate::Error::GetRegistryValue(KITS_ROOT_REGVALUE_NAME.to_string()))?;
|
|
|
-
|
|
|
- // Construct Windows SDK bin path
|
|
|
- let kits_root_10_bin_path = Path::new(&kits_root_10_path).join("bin");
|
|
|
-
|
|
|
- let mut installed_kits: Vec<String> = installed_roots_key
|
|
|
- .enum_keys()
|
|
|
- /* Report and ignore errors, pass on values. */
|
|
|
- .filter_map(|res| match res {
|
|
|
- Ok(v) => Some(v),
|
|
|
- Err(_) => None,
|
|
|
+#[cfg(windows)]
|
|
|
+fn signtool() -> Option<PathBuf> {
|
|
|
+ // sign code forked from https://github.com/forbjok/rust-codesign
|
|
|
+ static SIGN_TOOL: OnceLock<crate::Result<PathBuf>> = OnceLock::new();
|
|
|
+ SIGN_TOOL
|
|
|
+ .get_or_init(|| {
|
|
|
+ const INSTALLED_ROOTS_REGKEY_PATH: &str = r"SOFTWARE\Microsoft\Windows Kits\Installed Roots";
|
|
|
+ const KITS_ROOT_REGVALUE_NAME: &str = r"KitsRoot10";
|
|
|
+
|
|
|
+ // Open 32-bit HKLM "Installed Roots" key
|
|
|
+ let installed_roots_key = windows_registry::LOCAL_MACHINE
|
|
|
+ .open(INSTALLED_ROOTS_REGKEY_PATH)
|
|
|
+ .map_err(|_| crate::Error::OpenRegistry(INSTALLED_ROOTS_REGKEY_PATH.to_string()))?;
|
|
|
+
|
|
|
+ // Get the Windows SDK root path
|
|
|
+ let kits_root_10_path: String = installed_roots_key
|
|
|
+ .get_string(KITS_ROOT_REGVALUE_NAME)
|
|
|
+ .map_err(|_| crate::Error::GetRegistryValue(KITS_ROOT_REGVALUE_NAME.to_string()))?;
|
|
|
+
|
|
|
+ // Construct Windows SDK bin path
|
|
|
+ let kits_root_10_bin_path = Path::new(&kits_root_10_path).join("bin");
|
|
|
+
|
|
|
+ let mut installed_kits: Vec<String> = installed_roots_key
|
|
|
+ .keys()
|
|
|
+ .map_err(|_| crate::Error::FailedToEnumerateRegKeys)?
|
|
|
+ .collect();
|
|
|
+
|
|
|
+ // Sort installed kits
|
|
|
+ installed_kits.sort();
|
|
|
+
|
|
|
+ /* Iterate through installed kit version keys in reverse (from newest to oldest),
|
|
|
+ adding their bin paths to the list.
|
|
|
+ Windows SDK 10 v10.0.15063.468 and later will have their signtools located there. */
|
|
|
+ let mut kit_bin_paths: Vec<PathBuf> = installed_kits
|
|
|
+ .iter()
|
|
|
+ .rev()
|
|
|
+ .map(|kit| kits_root_10_bin_path.join(kit))
|
|
|
+ .collect();
|
|
|
+
|
|
|
+ /* Add kits root bin path.
|
|
|
+ For Windows SDK 10 versions earlier than v10.0.15063.468, signtool will be located there. */
|
|
|
+ kit_bin_paths.push(kits_root_10_bin_path);
|
|
|
+
|
|
|
+ // Choose which version of SignTool to use based on OS bitness
|
|
|
+ let arch_dir = util::os_bitness().ok_or(crate::Error::UnsupportedBitness)?;
|
|
|
+
|
|
|
+ /* Iterate through all bin paths, checking for existence of a SignTool executable. */
|
|
|
+ for kit_bin_path in &kit_bin_paths {
|
|
|
+ /* Construct SignTool path. */
|
|
|
+ let signtool_path = kit_bin_path.join(arch_dir).join("signtool.exe");
|
|
|
+
|
|
|
+ /* Check if SignTool exists at this location. */
|
|
|
+ if signtool_path.exists() {
|
|
|
+ // SignTool found. Return it.
|
|
|
+ return Ok(signtool_path);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Err(crate::Error::SignToolNotFound)
|
|
|
})
|
|
|
- .collect();
|
|
|
-
|
|
|
- // Sort installed kits
|
|
|
- installed_kits.sort();
|
|
|
-
|
|
|
- /* Iterate through installed kit version keys in reverse (from newest to oldest),
|
|
|
- adding their bin paths to the list.
|
|
|
- Windows SDK 10 v10.0.15063.468 and later will have their signtools located there. */
|
|
|
- let mut kit_bin_paths: Vec<PathBuf> = installed_kits
|
|
|
- .iter()
|
|
|
- .rev()
|
|
|
- .map(|kit| kits_root_10_bin_path.join(kit))
|
|
|
- .collect();
|
|
|
-
|
|
|
- /* Add kits root bin path.
|
|
|
- For Windows SDK 10 versions earlier than v10.0.15063.468, signtool will be located there. */
|
|
|
- kit_bin_paths.push(kits_root_10_bin_path);
|
|
|
-
|
|
|
- // Choose which version of SignTool to use based on OS bitness
|
|
|
- let arch_dir = util::os_bitness().ok_or(crate::Error::UnsupportedBitness)?;
|
|
|
-
|
|
|
- /* Iterate through all bin paths, checking for existence of a SignTool executable. */
|
|
|
- for kit_bin_path in &kit_bin_paths {
|
|
|
- /* Construct SignTool path. */
|
|
|
- let signtool_path = kit_bin_path.join(arch_dir).join("signtool.exe");
|
|
|
-
|
|
|
- /* Check if SignTool exists at this location. */
|
|
|
- if signtool_path.exists() {
|
|
|
- // SignTool found. Return it.
|
|
|
- return Ok(signtool_path);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- Err(crate::Error::SignToolNotFound)
|
|
|
+ .as_ref()
|
|
|
+ .ok()
|
|
|
+ .cloned()
|
|
|
}
|
|
|
|
|
|
/// Check if binary is already signed.
|
|
|
/// Used to skip sidecar binaries that are already signed.
|
|
|
+#[cfg(windows)]
|
|
|
pub fn verify(path: &Path) -> crate::Result<bool> {
|
|
|
- // Construct SignTool command
|
|
|
- let signtool = locate_signtool()?;
|
|
|
+ let signtool = signtool().ok_or(crate::Error::SignToolNotFound)?;
|
|
|
|
|
|
let mut cmd = Command::new(signtool);
|
|
|
cmd.arg("verify");
|
|
@@ -101,9 +131,31 @@ pub fn verify(path: &Path) -> crate::Result<bool> {
|
|
|
Ok(cmd.status()?.success())
|
|
|
}
|
|
|
|
|
|
-pub fn sign_command(path: &str, params: &SignParams) -> crate::Result<(Command, PathBuf)> {
|
|
|
- // Construct SignTool command
|
|
|
- let signtool = locate_signtool()?;
|
|
|
+pub fn sign_command_custom<P: AsRef<Path>>(path: P, command: &str) -> crate::Result<Command> {
|
|
|
+ let path = path.as_ref();
|
|
|
+
|
|
|
+ let mut args = command.trim().split(' ');
|
|
|
+ let bin = args
|
|
|
+ .next()
|
|
|
+ .context("custom signing command doesn't contain a bin?")?;
|
|
|
+
|
|
|
+ let mut cmd = Command::new(bin);
|
|
|
+ for arg in args {
|
|
|
+ if arg == "%1" {
|
|
|
+ cmd.arg(path);
|
|
|
+ } else {
|
|
|
+ cmd.arg(arg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Ok(cmd)
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg(windows)]
|
|
|
+pub fn sign_command_default<P: AsRef<Path>>(
|
|
|
+ path: P,
|
|
|
+ params: &SignParams,
|
|
|
+) -> crate::Result<Command> {
|
|
|
+ let signtool = signtool().ok_or(crate::Error::SignToolNotFound)?;
|
|
|
|
|
|
let mut cmd = Command::new(&signtool);
|
|
|
cmd.arg("sign");
|
|
@@ -120,17 +172,46 @@ pub fn sign_command(path: &str, params: &SignParams) -> crate::Result<(Command,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- cmd.arg(path);
|
|
|
+ cmd.arg(path.as_ref());
|
|
|
|
|
|
- Ok((cmd, signtool))
|
|
|
+ Ok(cmd)
|
|
|
}
|
|
|
|
|
|
-pub fn sign<P: AsRef<Path>>(path: P, params: &SignParams) -> crate::Result<()> {
|
|
|
- let path_str = path.as_ref().to_str().unwrap();
|
|
|
+pub fn sign_command<P: AsRef<Path>>(path: P, params: &SignParams) -> crate::Result<Command> {
|
|
|
+ match ¶ms.sign_command {
|
|
|
+ Some(custom_command) => sign_command_custom(path, custom_command),
|
|
|
+ #[cfg(windows)]
|
|
|
+ None => sign_command_default(path, params),
|
|
|
+
|
|
|
+ // should not be reachable
|
|
|
+ #[cfg(not(windows))]
|
|
|
+ None => Ok(Command::new("")),
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+pub fn sign_custom<P: AsRef<Path>>(path: P, custom_command: &str) -> crate::Result<()> {
|
|
|
+ let path = path.as_ref();
|
|
|
|
|
|
- log::info!(action = "Signing"; "{} with identity \"{}\"", path_str, params.certificate_thumbprint);
|
|
|
+ log::info!(action = "Signing";"{} with a custom signing command", tauri_utils::display_path(path));
|
|
|
|
|
|
- let (mut cmd, signtool) = sign_command(path_str, params)?;
|
|
|
+ let mut cmd = sign_command_custom(path, custom_command)?;
|
|
|
+
|
|
|
+ let output = cmd.output_ok()?;
|
|
|
+
|
|
|
+ let stdout = String::from_utf8_lossy(output.stdout.as_slice()).into_owned();
|
|
|
+ log::info!("{:?}", stdout);
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg(windows)]
|
|
|
+pub fn sign_default<P: AsRef<Path>>(path: P, params: &SignParams) -> crate::Result<()> {
|
|
|
+ let signtool = signtool().ok_or(crate::Error::SignToolNotFound)?;
|
|
|
+ let path = path.as_ref();
|
|
|
+
|
|
|
+ log::info!(action = "Signing"; "{} with identity \"{}\"", tauri_utils::display_path(path), params.certificate_thumbprint);
|
|
|
+
|
|
|
+ let mut cmd = sign_command_default(path, params)?;
|
|
|
log::debug!("Running signtool {:?}", signtool);
|
|
|
|
|
|
// Execute SignTool command
|
|
@@ -142,31 +223,15 @@ pub fn sign<P: AsRef<Path>>(path: P, params: &SignParams) -> crate::Result<()> {
|
|
|
Ok(())
|
|
|
}
|
|
|
|
|
|
-impl Settings {
|
|
|
- pub(crate) fn can_sign(&self) -> bool {
|
|
|
- self.windows().certificate_thumbprint.is_some()
|
|
|
- }
|
|
|
- pub(crate) fn sign_params(&self) -> SignParams {
|
|
|
- SignParams {
|
|
|
- product_name: self.product_name().into(),
|
|
|
- digest_algorithm: self
|
|
|
- .windows()
|
|
|
- .digest_algorithm
|
|
|
- .as_ref()
|
|
|
- .map(|algorithm| algorithm.to_string())
|
|
|
- .unwrap_or_else(|| "sha256".to_string()),
|
|
|
- certificate_thumbprint: self
|
|
|
- .windows()
|
|
|
- .certificate_thumbprint
|
|
|
- .clone()
|
|
|
- .unwrap_or_default(),
|
|
|
- timestamp_url: self
|
|
|
- .windows()
|
|
|
- .timestamp_url
|
|
|
- .as_ref()
|
|
|
- .map(|url| url.to_string()),
|
|
|
- tsp: self.windows().tsp,
|
|
|
- }
|
|
|
+pub fn sign<P: AsRef<Path>>(path: P, params: &SignParams) -> crate::Result<()> {
|
|
|
+ match ¶ms.sign_command {
|
|
|
+ Some(custom_command) => sign_custom(path, custom_command),
|
|
|
+ #[cfg(windows)]
|
|
|
+ None => sign_default(path, params),
|
|
|
+ // should not be reachable, as user should either use Windows
|
|
|
+ // or specify a custom sign_command but we succeed anyways
|
|
|
+ #[cfg(not(windows))]
|
|
|
+ None => Ok(()),
|
|
|
}
|
|
|
}
|
|
|
|