From 2a06d10066a806e392efe8bfb16d943ee0b0b61d Mon Sep 17 00:00:00 2001 From: SHIGRAF SALIK <140247389+ShigrafS@users.noreply.github.com> Date: Mon, 1 Sep 2025 22:29:55 +0530 Subject: [PATCH] =?UTF-8?q?feat(bundle):=20add=20--no-sign=20flag=20to=20s?= =?UTF-8?q?kip=20code=20signing=20in=20bundling=20pro=E2=80=A6=20(#14052)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(bundle): add --no-sign flag to skip code signing in bundling process - Introduce a o_sign option in bundle settings to allow skipping code signing - Update macOS and Windows bundler implementations to respect the flag - Wire up CLI option --no-sign to control signing behavior during bundling - Add necessary config and type changes to propagate the flag throughout bundler Signed-off-by: ShigrafS * ci: added yml for github action testing Signed-off-by: ShigrafS * fix: fixed field 'digest_algorithm' is already declared error Signed-off-by: ShigrafS * ci: updated to test the new features as well Signed-off-by: ShigrafS * ci: fixed yml issue Signed-off-by: ShigrafS * fix: fixed missing parameter issue in android sign.rs Signed-off-by: ShigrafS * chore: apply linting Signed-off-by: ShigrafS * chore: remove redundant files Signed-off-by: ShigrafS * chore: revert indentations Signed-off-by: ShigrafS * fix: added parameters to ios mobile build.rs Signed-off-by: ShigrafS * docs: updated documentation for settigs.rs Signed-off-by: ShigrafS * docs(cli): add documentation for o_sign flag in build options Signed-off-by: ShigrafS * chore: apply cargo fmt Signed-off-by: ShigrafS * docs: added CHANGES.md Signed-off-by: ShigrafS * refactor(bundler): make o_sign private and add getter Signed-off-by: ShigrafS * fix: minor error Signed-off-by: ShigrafS * refactor: revert build_benchmark_jsons.rs Signed-off-by: ShigrafS * impl for macos too * fix ci * fix windows build --------- Signed-off-by: ShigrafS Co-authored-by: Lucas Nogueira --- .changes/CHANGES.md | 6 ++ crates/tauri-bundler/src/bundle.rs | 88 +++++++++++-------- crates/tauri-bundler/src/bundle/macos/app.rs | 6 +- .../tauri-bundler/src/bundle/macos/dmg/mod.rs | 2 +- crates/tauri-bundler/src/bundle/settings.rs | 27 ++++++ .../src/bundle/windows/msi/mod.rs | 12 +-- .../src/bundle/windows/nsis/mod.rs | 12 +-- .../tauri-bundler/src/bundle/windows/sign.rs | 13 +-- crates/tauri-cli/src/build.rs | 7 ++ crates/tauri-cli/src/bundle.rs | 10 +++ crates/tauri-cli/src/mobile/android/build.rs | 1 + crates/tauri-cli/src/mobile/ios/build.rs | 1 + 12 files changed, 128 insertions(+), 57 deletions(-) create mode 100644 .changes/CHANGES.md diff --git a/.changes/CHANGES.md b/.changes/CHANGES.md new file mode 100644 index 000000000..132f1aa2e --- /dev/null +++ b/.changes/CHANGES.md @@ -0,0 +1,6 @@ +--- +'tauri-cli': 'minor:feat' +'tauri-bundler': 'minor:feat' +--- + +Add a `--no-sign` flag to the `tauri build` and `tauri bundle` commands to skip the code signing step, improving the developer experience for local testing and development without requiring code signing keys. diff --git a/crates/tauri-bundler/src/bundle.rs b/crates/tauri-bundler/src/bundle.rs index 3ee307eef..113b591ff 100644 --- a/crates/tauri-bundler/src/bundle.rs +++ b/crates/tauri-bundler/src/bundle.rs @@ -85,41 +85,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { } // Sign windows binaries before the bundling step in case neither wix and nsis bundles are enabled - if matches!(target_os, TargetPlatform::Windows) { - if settings.can_sign() { - for bin in settings.binaries() { - if bin.main() { - // we will sign the main binary after patching per "package type" - continue; - } - let bin_path = settings.binary_path(bin); - windows::sign::try_sign(&bin_path, settings)?; - } - - // Sign the sidecar binaries - for bin in settings.external_binaries() { - let path = bin?; - let skip = std::env::var("TAURI_SKIP_SIDECAR_SIGNATURE_CHECK").is_ok_and(|v| v == "true"); - if skip { - continue; - } - - #[cfg(windows)] - if windows::sign::verify(&path)? { - log::info!( - "sidecar at \"{}\" already signed. Skipping...", - path.display() - ); - 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..."); - } - } + sign_binaries_if_needed(settings, target_os)?; let main_binary = settings .binaries() @@ -134,8 +100,9 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { // - codesigning tools should handle calculating+updating this, we just need to ensure // (re)signing is performed after every `patch_binary()` operation // - signing an already-signed binary can result in multiple signatures, causing verification errors - let main_binary_reset_required = - matches!(target_os, TargetPlatform::Windows) && settings.can_sign() && package_types.len() > 1; + let main_binary_reset_required = matches!(target_os, TargetPlatform::Windows) + && settings.windows().can_sign() + && package_types.len() > 1; let mut unsigned_main_binary_copy = tempfile::tempfile()?; if main_binary_reset_required { let mut unsigned_main_binary = std::fs::File::open(&main_binary_path)?; @@ -155,7 +122,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { } // sign main binary for every package type after patch - if matches!(target_os, TargetPlatform::Windows) && settings.can_sign() { + if matches!(target_os, TargetPlatform::Windows) && settings.windows().can_sign() { if main_binary_signed && main_binary_reset_required { let mut signed_main_binary = std::fs::OpenOptions::new() .write(true) @@ -305,6 +272,51 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { Ok(bundles) } +fn sign_binaries_if_needed(settings: &Settings, target_os: &TargetPlatform) -> crate::Result<()> { + if matches!(target_os, TargetPlatform::Windows) { + if settings.windows().can_sign() { + if settings.no_sign() { + log::info!("Skipping binary signing due to --no-sign flag."); + return Ok(()); + } + + for bin in settings.binaries() { + if bin.main() { + // we will sign the main binary after patching per "package type" + continue; + } + let bin_path = settings.binary_path(bin); + windows::sign::try_sign(&bin_path, settings)?; + } + + // Sign the sidecar binaries + for bin in settings.external_binaries() { + let path = bin?; + let skip = std::env::var("TAURI_SKIP_SIDECAR_SIGNATURE_CHECK").is_ok_and(|v| v == "true"); + if skip { + continue; + } + + #[cfg(windows)] + if windows::sign::verify(&path)? { + log::info!( + "sidecar at \"{}\" already signed. Skipping...", + path.display() + ); + 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..."); + } + } + + Ok(()) +} + /// Check to see if there are icons in the settings struct pub fn check_icons(settings: &Settings) -> crate::Result { // make a peekable iterator of the icon_files diff --git a/crates/tauri-bundler/src/bundle/macos/app.rs b/crates/tauri-bundler/src/bundle/macos/app.rs index b5ef1f816..a6881ee10 100644 --- a/crates/tauri-bundler/src/bundle/macos/app.rs +++ b/crates/tauri-bundler/src/bundle/macos/app.rs @@ -103,7 +103,11 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { copy_custom_files_to_bundle(&bundle_directory, settings)?; - if let Some(keychain) = super::sign::keychain(settings.macos().signing_identity.as_deref())? { + if settings.no_sign() { + log::warn!("Skipping signing due to --no-sign flag.",); + } else if let Some(keychain) = + super::sign::keychain(settings.macos().signing_identity.as_deref())? + { // Sign frameworks and sidecar binaries first, per apple, signing must be done inside out // https://developer.apple.com/forums/thread/701514 sign_paths.push(SignTarget { diff --git a/crates/tauri-bundler/src/bundle/macos/dmg/mod.rs b/crates/tauri-bundler/src/bundle/macos/dmg/mod.rs index bbc59e505..eda756329 100644 --- a/crates/tauri-bundler/src/bundle/macos/dmg/mod.rs +++ b/crates/tauri-bundler/src/bundle/macos/dmg/mod.rs @@ -195,7 +195,7 @@ pub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result< // Sign DMG if needed // skipping self-signing DMGs https://github.com/tauri-apps/tauri/issues/12288 let identity = settings.macos().signing_identity.as_deref(); - if identity != Some("-") { + if !settings.no_sign() && identity != Some("-") { if let Some(keychain) = super::sign::keychain(identity)? { super::sign::sign( &keychain, diff --git a/crates/tauri-bundler/src/bundle/settings.rs b/crates/tauri-bundler/src/bundle/settings.rs index 85642e2e6..8703b5bb3 100644 --- a/crates/tauri-bundler/src/bundle/settings.rs +++ b/crates/tauri-bundler/src/bundle/settings.rs @@ -572,6 +572,12 @@ pub struct WindowsSettings { pub sign_command: Option, } +impl WindowsSettings { + pub(crate) fn can_sign(&self) -> bool { + self.sign_command.is_some() || self.certificate_thumbprint.is_some() + } +} + #[allow(deprecated)] mod _default { use super::*; @@ -778,6 +784,8 @@ pub struct Settings { target_platform: TargetPlatform, /// The target triple. target: String, + /// Whether to disable code signing during the bundling process. + no_sign: bool, } /// A builder for [`Settings`]. @@ -791,6 +799,7 @@ pub struct SettingsBuilder { binaries: Vec, target: Option, local_tools_directory: Option, + no_sign: bool, } impl SettingsBuilder { @@ -860,6 +869,13 @@ impl SettingsBuilder { self } + /// Sets whether to skip code signing. + #[must_use] + pub fn no_sign(mut self, no_sign: bool) -> Self { + self.no_sign = no_sign; + self + } + /// Builds a Settings from the CLI args. /// /// Package settings will be read from Cargo.toml. @@ -894,6 +910,7 @@ impl SettingsBuilder { }, target_platform, target, + no_sign: self.no_sign, }) } } @@ -1242,4 +1259,14 @@ impl Settings { pub fn updater(&self) -> Option<&UpdaterSettings> { self.bundle_settings.updater.as_ref() } + + /// Whether to skip signing. + pub fn no_sign(&self) -> bool { + self.no_sign + } + + /// Set whether to skip signing. + pub fn set_no_sign(&mut self, no_sign: bool) { + self.no_sign = no_sign; + } } diff --git a/crates/tauri-bundler/src/bundle/windows/msi/mod.rs b/crates/tauri-bundler/src/bundle/windows/msi/mod.rs index 9e59da3f4..84ed46c33 100644 --- a/crates/tauri-bundler/src/bundle/windows/msi/mod.rs +++ b/crates/tauri-bundler/src/bundle/windows/msi/mod.rs @@ -470,7 +470,7 @@ pub fn build_wix_app_installer( fs::create_dir_all(&output_path)?; // when we're performing code signing, we'll sign some WiX DLLs, so we make a local copy - let wix_toolset_path = if settings.can_sign() { + let wix_toolset_path = if settings.windows().can_sign() { let wix_path = output_path.join("wix"); crate::utils::fs_utils::copy_dir(wix_toolset_path, &wix_path) .context("failed to copy wix directory")?; @@ -771,7 +771,7 @@ pub fn build_wix_app_installer( let mut extensions = Vec::new(); for cap in extension_regex.captures_iter(&fragment) { let path = wix_toolset_path.join(format!("Wix{}.dll", &cap[1])); - if settings.can_sign() { + if settings.windows().can_sign() { try_sign(&path, settings)?; } extensions.push(path); @@ -785,7 +785,7 @@ pub fn build_wix_app_installer( fragment_extensions.insert(wix_toolset_path.join("WixUtilExtension.dll")); // sign default extensions - if settings.can_sign() { + if settings.windows().can_sign() { for path in &fragment_extensions { try_sign(path, settings)?; } @@ -879,7 +879,7 @@ pub fn build_wix_app_installer( )?; fs::rename(&msi_output_path, &msi_path)?; - if settings.can_sign() { + if settings.windows().can_sign() { try_sign(&msi_path, settings)?; } @@ -988,7 +988,7 @@ fn generate_resource_data(settings: &Settings) -> crate::Result { } added_resources.push(resource_path.clone()); - if settings.can_sign() && should_sign(&resource_path)? { + if settings.windows().can_sign() && should_sign(&resource_path)? { try_sign(&resource_path, settings)?; } @@ -1076,7 +1076,7 @@ fn generate_resource_data(settings: &Settings) -> crate::Result { .to_string_lossy() .into_owned(); if !added_resources.iter().any(|r| r.ends_with(&relative_path)) { - if settings.can_sign() { + if settings.windows().can_sign() { try_sign(resource_path, settings)?; } diff --git a/crates/tauri-bundler/src/bundle/windows/nsis/mod.rs b/crates/tauri-bundler/src/bundle/windows/nsis/mod.rs index 3469f2b3a..edd4acbe1 100644 --- a/crates/tauri-bundler/src/bundle/windows/nsis/mod.rs +++ b/crates/tauri-bundler/src/bundle/windows/nsis/mod.rs @@ -192,7 +192,7 @@ fn build_nsis_app_installer( // we make a copy of the NSIS directory if we're going to sign its DLLs // because we don't want to change the DLL hashes so the cache can reuse it - let maybe_plugin_copy_path = if settings.can_sign() { + let maybe_plugin_copy_path = if settings.windows().can_sign() { // find nsis path #[cfg(target_os = "linux")] let system_nsis_toolset_path = std::env::var_os("NSIS_PATH") @@ -283,7 +283,7 @@ fn build_nsis_app_installer( ); data.insert("copyright", to_json(settings.copyright_string())); - if settings.can_sign() { + if settings.windows().can_sign() { let sign_cmd = format!("{:?}", sign_command("%1", &settings.sign_params())?); data.insert("uninstaller_sign_cmd", to_json(sign_cmd)); } @@ -600,7 +600,7 @@ fn build_nsis_app_installer( )); fs::create_dir_all(nsis_installer_path.parent().unwrap())?; - if settings.can_sign() { + if settings.windows().can_sign() { log::info!("Signing NSIS plugins"); for dll in NSIS_PLUGIN_FILES { let path = additional_plugins_path.join(dll); @@ -640,7 +640,7 @@ fn build_nsis_app_installer( fs::rename(nsis_output_path, &nsis_installer_path)?; - if settings.can_sign() { + if settings.windows().can_sign() { try_sign(&nsis_installer_path, settings)?; } else { #[cfg(not(target_os = "windows"))] @@ -718,7 +718,7 @@ fn generate_resource_data(settings: &Settings) -> crate::Result { let loader_path = dunce::simplified(&settings.project_out_directory().join("WebView2Loader.dll")).to_path_buf(); if loader_path.exists() { - if settings.can_sign() { + if settings.windows().can_sign() { try_sign(&loader_path, settings)?; } added_resources.push(loader_path.clone()); @@ -743,7 +743,7 @@ fn generate_resource_data(settings: &Settings) -> crate::Result { } added_resources.push(resource_path.clone()); - if settings.can_sign() && should_sign(&resource_path)? { + if settings.windows().can_sign() && should_sign(&resource_path)? { try_sign(&resource_path, settings)?; } diff --git a/crates/tauri-bundler/src/bundle/windows/sign.rs b/crates/tauri-bundler/src/bundle/windows/sign.rs index 40ddfc69c..04e5a2da5 100644 --- a/crates/tauri-bundler/src/bundle/windows/sign.rs +++ b/crates/tauri-bundler/src/bundle/windows/sign.rs @@ -14,10 +14,6 @@ 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(), @@ -251,7 +247,14 @@ pub fn sign>(path: P, params: &SignParams) -> crate::Result<()> { } pub fn try_sign>(file_path: P, settings: &Settings) -> crate::Result<()> { - if settings.can_sign() { + 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())?; } diff --git a/crates/tauri-cli/src/build.rs b/crates/tauri-cli/src/build.rs index 12138efcc..288518d87 100644 --- a/crates/tauri-cli/src/build.rs +++ b/crates/tauri-cli/src/build.rs @@ -76,11 +76,18 @@ pub struct Options { /// Only use this when you are sure the mismatch is incorrectly detected as version mismatched Tauri packages can lead to unknown behavior. #[clap(long)] pub ignore_version_mismatches: bool, + /// Skip code signing when bundling the app + #[clap(long)] + pub no_sign: bool, } pub fn command(mut options: Options, verbosity: u8) -> Result<()> { crate::helpers::app_paths::resolve(); + if options.no_sign { + log::warn!("--no-sign flag detected: Signing will be skipped."); + } + let ci = options.ci; let target = options diff --git a/crates/tauri-cli/src/bundle.rs b/crates/tauri-cli/src/bundle.rs index 09bb38777..23999082f 100644 --- a/crates/tauri-cli/src/bundle.rs +++ b/crates/tauri-cli/src/bundle.rs @@ -92,6 +92,14 @@ pub struct Options { /// On subsequent runs, it's recommended to disable this setting again. #[clap(long)] pub skip_stapling: bool, + + /// Skip code signing during the build or bundling process. + /// + /// Useful for local development and CI environments + /// where signing certificates or environment variables + /// are not available or not needed. + #[clap(long)] + pub no_sign: bool, } impl From for Options { @@ -104,6 +112,7 @@ impl From for Options { ci: value.ci, config: value.config, skip_stapling: value.skip_stapling, + no_sign: value.no_sign, } } } @@ -197,6 +206,7 @@ pub fn bundle( let mut settings = app_settings .get_bundler_settings(options.clone().into(), config, out_dir, package_types) .with_context(|| "failed to build bundler settings")?; + settings.set_no_sign(options.no_sign); settings.set_log_level(match verbosity { 0 => log::Level::Error, diff --git a/crates/tauri-cli/src/mobile/android/build.rs b/crates/tauri-cli/src/mobile/android/build.rs index e042979b9..94f5b3896 100644 --- a/crates/tauri-cli/src/mobile/android/build.rs +++ b/crates/tauri-cli/src/mobile/android/build.rs @@ -99,6 +99,7 @@ impl From for BuildOptions { ci: options.ci, skip_stapling: false, ignore_version_mismatches: options.ignore_version_mismatches, + no_sign: false, } } } diff --git a/crates/tauri-cli/src/mobile/ios/build.rs b/crates/tauri-cli/src/mobile/ios/build.rs index 4f0de4f37..27f06a608 100644 --- a/crates/tauri-cli/src/mobile/ios/build.rs +++ b/crates/tauri-cli/src/mobile/ios/build.rs @@ -150,6 +150,7 @@ impl From for BuildOptions { ci: options.ci, skip_stapling: false, ignore_version_mismatches: options.ignore_version_mismatches, + no_sign: false, } } }