diff --git a/.changes/custom-sign-command.md b/.changes/custom-sign-command.md new file mode 100644 index 000000000000..606ac05b9c17 --- /dev/null +++ b/.changes/custom-sign-command.md @@ -0,0 +1,5 @@ +--- +"tauri-bundler": "patch:feat" +--- + +On Windows, add option to specify a custom signing command to be used. This opens an endless possibilities, for example use `osslsigncode` on non-Windows or use hardware tokens and HSM or even using Azure Trusted Signing. diff --git a/.changes/utils-sign-command.md b/.changes/utils-sign-command.md new file mode 100644 index 000000000000..67b0e39d7170 --- /dev/null +++ b/.changes/utils-sign-command.md @@ -0,0 +1,5 @@ +--- +"tauri-utils": "patch:feat" +--- + +Add `sign_command` in `WindowsConfig` diff --git a/core/tauri-config-schema/schema.json b/core/tauri-config-schema/schema.json index 7a8a234fc612..617124fcabbd 100644 --- a/core/tauri-config-schema/schema.json +++ b/core/tauri-config-schema/schema.json @@ -112,6 +112,7 @@ "certificateThumbprint": null, "digestAlgorithm": null, "nsis": null, + "signCommand": null, "timestampUrl": null, "tsp": false, "webviewFixedRuntimePath": null, @@ -1619,6 +1620,7 @@ "certificateThumbprint": null, "digestAlgorithm": null, "nsis": null, + "signCommand": null, "timestampUrl": null, "tsp": false, "webviewFixedRuntimePath": null, @@ -1977,6 +1979,13 @@ "type": "null" } ] + }, + "signCommand": { + "description": "Specify a custom command to sign the binaries. This command needs to have a `%1` in it which is just a placeholder for the binary path, which we will detect and replace before calling the command.\n\nExample: ```text sign-cli --arg1 --arg2 %1 ```\n\nBy Default we use `signtool.exe` which can be found only on Windows so if you are on another platform and want to cross-compile and sign you will need to use another tool like `osslsigncode`.", + "type": [ + "string", + "null" + ] } }, "additionalProperties": false diff --git a/core/tauri-utils/src/config.rs b/core/tauri-utils/src/config.rs index c153f68e55e4..ea71c3cbd7a6 100644 --- a/core/tauri-utils/src/config.rs +++ b/core/tauri-utils/src/config.rs @@ -859,6 +859,20 @@ pub struct WindowsConfig { pub wix: Option, /// Configuration for the installer generated with NSIS. pub nsis: Option, + /// Specify a custom command to sign the binaries. + /// This command needs to have a `%1` in it which is just a placeholder for the binary path, + /// which we will detect and replace before calling the command. + /// + /// Example: + /// ```text + /// sign-cli --arg1 --arg2 %1 + /// ``` + /// + /// By Default we use `signtool.exe` which can be found only on Windows so + /// if you are on another platform and want to cross-compile and sign you will + /// need to use another tool like `osslsigncode`. + #[serde(alias = "sign-command")] + pub sign_command: Option, } impl Default for WindowsConfig { @@ -873,6 +887,7 @@ impl Default for WindowsConfig { allow_downgrades: true, wix: None, nsis: None, + sign_command: None, } } } diff --git a/tooling/bundler/Cargo.toml b/tooling/bundler/Cargo.toml index 28ad149ac8f8..a25fae9d0390 100644 --- a/tooling/bundler/Cargo.toml +++ b/tooling/bundler/Cargo.toml @@ -44,7 +44,7 @@ dunce = "1" [target."cfg(target_os = \"windows\")".dependencies] uuid = { version = "1", features = [ "v4", "v5" ] } bitness = "0.4" -winreg = "0.52" +windows-registry = "0.1.1" glob = "0.3" [target."cfg(target_os = \"windows\")".dependencies.windows-sys] diff --git a/tooling/bundler/src/bundle.rs b/tooling/bundler/src/bundle.rs index 681b50aafb07..b4a937550529 100644 --- a/tooling/bundler/src/bundle.rs +++ b/tooling/bundler/src/bundle.rs @@ -63,8 +63,7 @@ pub fn bundle_project(settings: Settings) -> crate::Result> { log::warn!("Cross-platform compilation is experimental and does not support all features. Please use a matching host system for full compatibility."); } - #[cfg(target_os = "windows")] - { + if settings.can_sign() { // Sign windows binaries before the bundling step in case neither wix and nsis bundles are enabled for bin in settings.binaries() { let bin_path = settings.binary_path(bin); @@ -75,16 +74,24 @@ pub fn bundle_project(settings: Settings) -> crate::Result> { for bin in settings.external_binaries() { let path = bin?; let skip = std::env::var("TAURI_SKIP_SIDECAR_SIGNATURE_CHECK").map_or(false, |v| v == "true"); + if skip { + continue; + } - if !skip && windows::sign::verify(&path)? { + #[cfg(windows)] + if windows::sign::verify(&path)? { log::info!( "sidecar at \"{}\" already signed. Skipping...", path.display() - ) - } else { - windows::sign::try_sign(&path, &settings)?; + ); + continue; } + + windows::sign::try_sign(&path, &settings)?; } + } else { + #[cfg(not(target_os = "windows"))] + log::warn!("Signing, by default, is only supported on Windows hosts, but you can specify a custom signing command in `bundler > windows > sign_command`, for now, skipping signing the installer..."); } for package_type in &package_types { diff --git a/tooling/bundler/src/bundle/settings.rs b/tooling/bundler/src/bundle/settings.rs index 5468e5e8a3fd..31c06b8986be 100644 --- a/tooling/bundler/src/bundle/settings.rs +++ b/tooling/bundler/src/bundle/settings.rs @@ -447,6 +447,20 @@ pub struct WindowsSettings { /// /// /// The default value of this flag is `true`. pub allow_downgrades: bool, + + /// Specify a custom command to sign the binaries. + /// This command needs to have a `%1` in it which is just a placeholder for the binary path, + /// which we will detect and replace before calling the command. + /// + /// Example: + /// ```text + /// sign-cli --arg1 --arg2 %1 + /// ``` + /// + /// By Default we use `signtool.exe` which can be found only on Windows so + /// if you are on another platform and want to cross-compile and sign you will + /// need to use another tool like `osslsigncode`. + pub sign_command: Option, } impl Default for WindowsSettings { @@ -462,6 +476,7 @@ impl Default for WindowsSettings { webview_install_mode: Default::default(), webview_fixed_runtime_path: None, allow_downgrades: true, + sign_command: None, } } } diff --git a/tooling/bundler/src/bundle/windows/mod.rs b/tooling/bundler/src/bundle/windows/mod.rs index 31eb7566d681..8c63b423eac5 100644 --- a/tooling/bundler/src/bundle/windows/mod.rs +++ b/tooling/bundler/src/bundle/windows/mod.rs @@ -6,7 +6,6 @@ #[cfg(target_os = "windows")] pub mod msi; pub mod nsis; -#[cfg(target_os = "windows")] pub mod sign; mod util; diff --git a/tooling/bundler/src/bundle/windows/msi/wix.rs b/tooling/bundler/src/bundle/windows/msi/wix.rs index 01dc85ba0a4e..11579abcb988 100644 --- a/tooling/bundler/src/bundle/windows/msi/wix.rs +++ b/tooling/bundler/src/bundle/windows/msi/wix.rs @@ -798,7 +798,11 @@ pub fn build_wix_app_installer( &msi_output_path, )?; rename(&msi_output_path, &msi_path)?; - try_sign(&msi_path, settings)?; + + if settings.can_sign() { + try_sign(&msi_path, settings)?; + } + output_paths.push(msi_path); } diff --git a/tooling/bundler/src/bundle/windows/nsis.rs b/tooling/bundler/src/bundle/windows/nsis.rs index 8a7c285a4569..49ec0165c077 100644 --- a/tooling/bundler/src/bundle/windows/nsis.rs +++ b/tooling/bundler/src/bundle/windows/nsis.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -#[cfg(target_os = "windows")] use crate::bundle::windows::sign::{sign_command, try_sign}; + use crate::{ bundle::{ common::CommandExt, @@ -67,6 +67,7 @@ pub fn bundle_project(settings: &Settings, updater: bool) -> crate::Result c NSIS_TAURI_UTILS_SHA1, HashAlgorithm::Sha1, )?; - write( - nsis_plugins - .join("x86-unicode") - .join("nsis_tauri_utils.dll"), - data, - )?; + + let target_folder = nsis_plugins.join("x86-unicode"); + create_dir_all(&target_folder)?; + write(target_folder.join("nsis_tauri_utils.dll"), data)?; Ok(()) } @@ -163,9 +162,6 @@ fn build_nsis_app_installer( log::info!("Target: {}", arch); - #[cfg(not(target_os = "windows"))] - log::info!("Code signing is currently only supported on Windows hosts, skipping..."); - let output_path = settings.project_out_directory().join("nsis").join(arch); if output_path.exists() { remove_dir_all(&output_path)?; @@ -197,16 +193,9 @@ fn build_nsis_app_installer( ); data.insert("copyright", to_json(settings.copyright_string())); - // Code signing is currently only supported on Windows hosts - #[cfg(target_os = "windows")] if settings.can_sign() { - data.insert( - "uninstaller_sign_cmd", - to_json(format!( - "{:?}", - sign_command("%1", &settings.sign_params())?.0 - )), - ); + let sign_cmd = format!("{:?}", sign_command("%1", &settings.sign_params())?); + data.insert("uninstaller_sign_cmd", to_json(sign_cmd)); } let version = settings.version_string(); @@ -517,9 +506,12 @@ fn build_nsis_app_installer( rename(nsis_output_path, &nsis_installer_path)?; - // Code signing is currently only supported on Windows hosts - #[cfg(target_os = "windows")] - try_sign(&nsis_installer_path, settings)?; + if settings.can_sign() { + try_sign(&nsis_installer_path, settings)?; + } else { + #[cfg(not(target_os = "windows"))] + log::warn!("Signing, by default, is only supported on Windows hosts, but you can specify a custom signing command in `bundler > windows > sign_command`, for now, skipping signing the installer..."); + } Ok(vec![nsis_installer_path]) } diff --git a/tooling/bundler/src/bundle/windows/sign.rs b/tooling/bundler/src/bundle/windows/sign.rs index 535af148d361..0ecdd2ee4751 100644 --- a/tooling/bundler/src/bundle/windows/sign.rs +++ b/tooling/bundler/src/bundle/windows/sign.rs @@ -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, pub tsp: bool, + pub sign_command: Option, } -// sign code forked from https://github.com/forbjok/rust-codesign -fn locate_signtool() -> crate::Result { - 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 = 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 { + // sign code forked from https://github.com/forbjok/rust-codesign + static SIGN_TOOL: OnceLock> = 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 = 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 = 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 = 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 { - // 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 { 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>(path: P, command: &str) -> crate::Result { + 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>( + path: P, + params: &SignParams, +) -> crate::Result { + 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>(path: P, params: &SignParams) -> crate::Result<()> { - let path_str = path.as_ref().to_str().unwrap(); +pub fn sign_command>(path: P, params: &SignParams) -> crate::Result { + 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>(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>(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>(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>(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(()), } } diff --git a/tooling/bundler/src/error.rs b/tooling/bundler/src/error.rs index 06793f871072..82b0b23a3a99 100644 --- a/tooling/bundler/src/error.rs +++ b/tooling/bundler/src/error.rs @@ -94,6 +94,9 @@ pub enum Error { /// Failed to get registry value. #[error("failed to get {0} value on registry")] GetRegistryValue(String), + /// Failed to enumerate registry keys. + #[error("failed to enumerate registry keys")] + FailedToEnumerateRegKeys, /// Unsupported OS bitness. #[error("unsupported OS bitness")] UnsupportedBitness, diff --git a/tooling/cli/Cargo.lock b/tooling/cli/Cargo.lock index 52aa4a245811..3aca3322fd56 100644 --- a/tooling/cli/Cargo.lock +++ b/tooling/cli/Cargo.lock @@ -4874,8 +4874,8 @@ dependencies = [ "ureq", "uuid", "walkdir", + "windows-registry", "windows-sys 0.52.0", - "winreg 0.52.0", "zip", ] @@ -5952,6 +5952,16 @@ dependencies = [ "syn 2.0.52", ] +[[package]] +name = "windows-registry" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f721bc2e55efb506a1a395a545cb76c2481fb023d33b51f0050e7888716281cf" +dependencies = [ + "windows-result", + "windows-targets 0.52.5", +] + [[package]] name = "windows-result" version = "0.1.1" @@ -6147,16 +6157,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "winreg" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "winsafe" version = "0.0.19" diff --git a/tooling/cli/schema.json b/tooling/cli/schema.json index 7a8a234fc612..617124fcabbd 100644 --- a/tooling/cli/schema.json +++ b/tooling/cli/schema.json @@ -112,6 +112,7 @@ "certificateThumbprint": null, "digestAlgorithm": null, "nsis": null, + "signCommand": null, "timestampUrl": null, "tsp": false, "webviewFixedRuntimePath": null, @@ -1619,6 +1620,7 @@ "certificateThumbprint": null, "digestAlgorithm": null, "nsis": null, + "signCommand": null, "timestampUrl": null, "tsp": false, "webviewFixedRuntimePath": null, @@ -1977,6 +1979,13 @@ "type": "null" } ] + }, + "signCommand": { + "description": "Specify a custom command to sign the binaries. This command needs to have a `%1` in it which is just a placeholder for the binary path, which we will detect and replace before calling the command.\n\nExample: ```text sign-cli --arg1 --arg2 %1 ```\n\nBy Default we use `signtool.exe` which can be found only on Windows so if you are on another platform and want to cross-compile and sign you will need to use another tool like `osslsigncode`.", + "type": [ + "string", + "null" + ] } }, "additionalProperties": false diff --git a/tooling/cli/src/interface/rust.rs b/tooling/cli/src/interface/rust.rs index ab6a458880a8..b7b7b95de600 100644 --- a/tooling/cli/src/interface/rust.rs +++ b/tooling/cli/src/interface/rust.rs @@ -1420,6 +1420,7 @@ fn tauri_config_to_bundle_settings( webview_install_mode: config.windows.webview_install_mode, webview_fixed_runtime_path: config.windows.webview_fixed_runtime_path, allow_downgrades: config.windows.allow_downgrades, + sign_command: config.windows.sign_command, }, license: config.license.or_else(|| { settings