use crate::bundle::settings::CustomSignCommandSettings;
#[cfg(windows)]
use crate::bundle::windows::util;
use crate::{utils::CommandExt, Settings};
#[cfg(windows)]
use std::path::PathBuf;
#[cfg(windows)]
use std::sync::OnceLock;
use std::{path::Path, process::Command};
impl Settings {
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(),
}
}
}
#[cfg_attr(not(windows), allow(dead_code))]
pub struct SignParams {
pub product_name: String,
pub digest_algorithm: String,
pub certificate_thumbprint: String,
pub timestamp_url: Option<String>,
pub tsp: bool,
pub sign_command: Option<CustomSignCommandSettings>,
}
#[cfg(windows)]
fn signtool() -> Option<PathBuf> {
static SIGN_TOOL: OnceLock<crate::Result<PathBuf>> = OnceLock::new();
SIGN_TOOL
.get_or_init(|| {
if let Some(signtool) = std::env::var_os("TAURI_WINDOWS_SIGNTOOL_PATH") {
return Ok(PathBuf::from(signtool));
}
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 = windows_registry::LOCAL_MACHINE
.open(INSTALLED_ROOTS_REGKEY_PATH)
.map_err(|_| crate::Error::OpenRegistry(INSTALLED_ROOTS_REGKEY_PATH.to_string()))?;
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()))?;
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();
installed_kits.sort();
let mut kit_bin_paths: Vec<PathBuf> = installed_kits
.iter()
.rev()
.map(|kit| kits_root_10_bin_path.join(kit))
.collect();
kit_bin_paths.push(kits_root_10_bin_path);
let arch_dir = util::processor_architecture().ok_or(crate::Error::UnsupportedBitness)?;
for kit_bin_path in &kit_bin_paths {
let signtool_path = kit_bin_path.join(arch_dir).join("signtool.exe");
if signtool_path.exists() {
return Ok(signtool_path);
}
}
Err(crate::Error::SignToolNotFound)
})
.as_ref()
.ok()
.cloned()
}
#[cfg(windows)]
pub fn verify(path: &Path) -> crate::Result<bool> {
let signtool = signtool().ok_or(crate::Error::SignToolNotFound)?;
let mut cmd = Command::new(signtool);
cmd.arg("verify");
cmd.arg("/pa");
cmd.arg(path);
Ok(cmd.status()?.success())
}
pub fn sign_command_custom<P: AsRef<Path>>(
path: P,
command: &CustomSignCommandSettings,
) -> crate::Result<Command> {
let path = path.as_ref();
let cwd = std::env::current_dir()?;
let mut cmd = Command::new(&command.cmd);
for arg in &command.args {
if arg == "%1" {
cmd.arg(path);
} else {
let path = Path::new(arg);
if path.exists() && path.is_relative() {
cmd.arg(cwd.join(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");
cmd.args(["/fd", ¶ms.digest_algorithm]);
cmd.args(["/sha1", ¶ms.certificate_thumbprint]);
cmd.args(["/d", ¶ms.product_name]);
if let Some(ref timestamp_url) = params.timestamp_url {
if params.tsp {
cmd.args(["/tr", timestamp_url]);
cmd.args(["/td", ¶ms.digest_algorithm]);
} else {
cmd.args(["/t", timestamp_url]);
}
}
cmd.arg(path.as_ref());
Ok(cmd)
}
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),
#[cfg(not(windows))]
None => Ok(Command::new("")),
}
}
pub fn sign_custom<P: AsRef<Path>>(
path: P,
custom_command: &CustomSignCommandSettings,
) -> crate::Result<()> {
let path = path.as_ref();
log::info!(action = "Signing";"{} with a custom signing command", tauri_utils::display_path(path));
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!(action = "Signing";"Output of signing command:\n{}", stdout.trim());
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);
let output = cmd.output_ok()?;
let stdout = String::from_utf8_lossy(output.stdout.as_slice()).into_owned();
log::info!(action = "Signing";"Output of signing command:\n{}", stdout.trim());
Ok(())
}
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),
#[cfg(not(windows))]
None => Ok(()),
}
}
pub fn try_sign<P: AsRef<Path>>(file_path: P, settings: &Settings) -> crate::Result<()> {
if settings.no_sign() {
log::warn!(
"Skipping signing for {} due to --no-sign flag.",
tauri_utils::display_path(file_path.as_ref())
);
return Ok(());
}
if settings.windows().can_sign() {
log::info!(action = "Signing"; "{}", tauri_utils::display_path(file_path.as_ref()));
sign(file_path, &settings.sign_params())?;
}
Ok(())
}
pub fn should_sign(file_path: &Path) -> crate::Result<bool> {
let is_binary = file_path
.extension()
.is_some_and(|ext| ext == "exe" || ext == "dll");
if !is_binary {
return Ok(false);
}
#[cfg(windows)]
{
let already_signed = verify(file_path)?;
Ok(!already_signed)
}
#[cfg(not(windows))]
{
Ok(true)
}
}