Переглянути джерело

feat(bundler): support custom sign command on Windows (#9865)

* feat(bundler): support custom sign command on Windows

closes #7188
closes #9578

* fix double quotes

* fix build

* fix build

* clippy

* Update sign.rs

* clippy && replace `winreg` with `windows-registry`

* remove log [skip ci]

* Apply suggestions from code review

* tweak arg so path with spaces work on macOS

* create nsis toolset paths

---------

Co-authored-by: Lucas Nogueira <lucas@tauri.app>
Amr Bashir 1 рік тому
батько
коміт
d6d3efbd12

+ 5 - 0
.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.

+ 5 - 0
.changes/utils-sign-command.md

@@ -0,0 +1,5 @@
+---
+"tauri-utils": "patch:feat"
+---
+
+Add `sign_command` in `WindowsConfig`

+ 9 - 0
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

+ 15 - 0
core/tauri-utils/src/config.rs

@@ -859,6 +859,20 @@ pub struct WindowsConfig {
   pub wix: Option<WixConfig>,
   /// Configuration for the installer generated with NSIS.
   pub nsis: Option<NsisConfig>,
+  /// 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<String>,
 }
 
 impl Default for WindowsConfig {
@@ -873,6 +887,7 @@ impl Default for WindowsConfig {
       allow_downgrades: true,
       wix: None,
       nsis: None,
+      sign_command: None,
     }
   }
 }

+ 1 - 1
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]

+ 13 - 6
tooling/bundler/src/bundle.rs

@@ -63,8 +63,7 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<Bundle>> {
     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<Vec<Bundle>> {
     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 {

+ 15 - 0
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<String>,
 }
 
 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,
     }
   }
 }

+ 0 - 1
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;

+ 5 - 1
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);
   }
 

+ 14 - 22
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<Vec<P
   let nsis_toolset_path = tauri_tools_path.join("NSIS");
 
   if !nsis_toolset_path.exists() {
+    create_dir_all(&nsis_toolset_path)?;
     get_and_extract_nsis(&nsis_toolset_path, &tauri_tools_path)?;
   } else if NSIS_REQUIRED_FILES
     .iter()
@@ -114,12 +115,10 @@ fn get_and_extract_nsis(nsis_toolset_path: &Path, _tauri_tools_path: &Path) -> 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])
 }

+ 173 - 108
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<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 &params.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 &params.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(()),
   }
 }
 

+ 3 - 0
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,

+ 11 - 11
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"

+ 9 - 0
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

+ 1 - 0
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