feat(bundle): add --no-sign flag to skip code signing in bundling pro… (#14052)

* 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 <shigrafsalik@proton.me>

* ci: added yml for github action testing

Signed-off-by: ShigrafS <shigrafsalik@proton.me>

* fix: fixed field 'digest_algorithm' is already declared error

Signed-off-by: ShigrafS <shigrafsalik@proton.me>

* ci: updated to test the new features as well

Signed-off-by: ShigrafS <shigrafsalik@proton.me>

* ci: fixed yml issue

Signed-off-by: ShigrafS <shigrafsalik@proton.me>

* fix: fixed missing parameter issue in android sign.rs

Signed-off-by: ShigrafS <shigrafsalik@proton.me>

* chore: apply linting

Signed-off-by: ShigrafS <shigrafsalik@proton.me>

* chore: remove redundant files

Signed-off-by: ShigrafS <shigrafsalik@proton.me>

* chore: revert indentations

Signed-off-by: ShigrafS <shigrafsalik@proton.me>

* fix: added parameters to ios mobile build.rs

Signed-off-by: ShigrafS <shigrafsalik@proton.me>

* docs: updated documentation for settigs.rs

Signed-off-by: ShigrafS <shigrafsalik@proton.me>

* docs(cli): add documentation for
o_sign flag in build options

Signed-off-by: ShigrafS <shigrafsalik@proton.me>

* chore: apply cargo fmt

Signed-off-by: ShigrafS <shigrafsalik@proton.me>

* docs: added CHANGES.md

Signed-off-by: ShigrafS <shigrafsalik@proton.me>

* refactor(bundler): make
o_sign private and add getter

Signed-off-by: ShigrafS <shigrafsalik@proton.me>

* fix: minor error

Signed-off-by: ShigrafS <shigrafsalik@proton.me>

* refactor: revert build_benchmark_jsons.rs

Signed-off-by: ShigrafS <shigrafsalik@proton.me>

* impl for macos too

* fix ci

* fix windows build

---------

Signed-off-by: ShigrafS <shigrafsalik@proton.me>
Co-authored-by: Lucas Nogueira <lucas@tauri.app>
This commit is contained in:
SHIGRAF SALIK
2025-09-01 22:29:55 +05:30
committed by GitHub
parent 59089723fc
commit 2a06d10066
12 changed files with 128 additions and 57 deletions

6
.changes/CHANGES.md Normal file
View File

@@ -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.

View File

@@ -85,41 +85,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
}
// 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<Vec<Bundle>> {
// - 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<Vec<Bundle>> {
}
// 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<Vec<Bundle>> {
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<bool> {
// make a peekable iterator of the icon_files

View File

@@ -103,7 +103,11 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
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 {

View File

@@ -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,

View File

@@ -572,6 +572,12 @@ pub struct WindowsSettings {
pub sign_command: Option<CustomSignCommandSettings>,
}
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<BundleBinary>,
target: Option<String>,
local_tools_directory: Option<PathBuf>,
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;
}
}

View File

@@ -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<ResourceMap> {
}
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<ResourceMap> {
.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)?;
}

View File

@@ -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<ResourcesMap> {
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<ResourcesMap> {
}
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)?;
}

View File

@@ -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<P: AsRef<Path>>(path: P, params: &SignParams) -> crate::Result<()> {
}
pub fn try_sign<P: AsRef<Path>>(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())?;
}

View File

@@ -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

View File

@@ -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<crate::build::Options> for Options {
@@ -104,6 +112,7 @@ impl From<crate::build::Options> 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<A: AppSettings>(
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,

View File

@@ -99,6 +99,7 @@ impl From<Options> for BuildOptions {
ci: options.ci,
skip_stapling: false,
ignore_version_mismatches: options.ignore_version_mismatches,
no_sign: false,
}
}
}

View File

@@ -150,6 +150,7 @@ impl From<Options> for BuildOptions {
ci: options.ci,
skip_stapling: false,
ignore_version_mismatches: options.ignore_version_mismatches,
no_sign: false,
}
}
}