Skip to content

Commit 8d994f6

Browse files
fix(bundler): sign DLLs (#11676)
* fix: sign nsis plugin DLLs * also sign DLLs on unix * fix build * create copy of nsis dir * always make a copy of nsis (so linux works, permission error otherwise) * fix windows build * fix * to_path_buf * also create wix copy * remove unused toolset change * fix unused var * fmt * fix wix build * fix build * fix plugin copy * fix conflict * fix file download --------- Co-authored-by: Lucas Nogueira <[email protected]>
1 parent 8a1d490 commit 8d994f6

File tree

10 files changed

+174
-35
lines changed

10 files changed

+174
-35
lines changed

.changes/nsis-sign-plugins.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'tauri-bundler': 'patch:enhance'
3+
---
4+
5+
Sign NSIS and WiX DLLs when bundling

.changes/sign-dlls.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'tauri-bundler': 'patch:enhance'
3+
---
4+
5+
Sign DLLs from resources.

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/tauri-bundler/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ ar = "0.9"
6565
md5 = "0.7"
6666
rpm = { version = "0.16", features = ["bzip2-compression"] }
6767

68+
[target."cfg(unix)".dependencies]
69+
which = "7"
70+
6871
[lib]
6972
name = "tauri_bundler"
7073
path = "src/lib.rs"

crates/tauri-bundler/src/bundle/windows/msi/mod.rs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,16 @@ pub fn build_wix_app_installer(
472472
}
473473
fs::create_dir_all(&output_path)?;
474474

475+
// when we're performing code signing, we'll sign some WiX DLLs, so we make a local copy
476+
let wix_toolset_path = if settings.can_sign() {
477+
let wix_path = output_path.join("wix");
478+
crate::utils::fs_utils::copy_dir(&wix_toolset_path, &wix_path)
479+
.context("failed to copy wix directory")?;
480+
wix_path
481+
} else {
482+
wix_toolset_path.to_path_buf()
483+
};
484+
475485
let mut data = BTreeMap::new();
476486

477487
let silent_webview_install = if let WebviewInstallMode::DownloadBootstrapper { silent }
@@ -763,7 +773,11 @@ pub fn build_wix_app_installer(
763773
let fragment = fragment_handlebars.render_template(&fragment_content, &data)?;
764774
let mut extensions = Vec::new();
765775
for cap in extension_regex.captures_iter(&fragment) {
766-
extensions.push(wix_toolset_path.join(format!("Wix{}.dll", &cap[1])));
776+
let path = wix_toolset_path.join(format!("Wix{}.dll", &cap[1]));
777+
if settings.can_sign() {
778+
try_sign(&path, settings)?;
779+
}
780+
extensions.push(path);
767781
}
768782
candle_inputs.push((fragment_path, extensions));
769783
}
@@ -773,11 +787,18 @@ pub fn build_wix_app_installer(
773787
fragment_extensions.insert(wix_toolset_path.join("WixUIExtension.dll"));
774788
fragment_extensions.insert(wix_toolset_path.join("WixUtilExtension.dll"));
775789

790+
// sign default extensions
791+
if settings.can_sign() {
792+
for path in &fragment_extensions {
793+
try_sign(&path, settings)?;
794+
}
795+
}
796+
776797
for (path, extensions) in candle_inputs {
777798
for ext in &extensions {
778799
fragment_extensions.insert(ext.clone());
779800
}
780-
run_candle(settings, wix_toolset_path, &output_path, path, extensions)?;
801+
run_candle(settings, &wix_toolset_path, &output_path, path, extensions)?;
781802
}
782803

783804
let mut output_paths = Vec::new();
@@ -853,7 +874,7 @@ pub fn build_wix_app_installer(
853874
log::info!(action = "Running"; "light to produce {}", display_path(&msi_path));
854875

855876
run_light(
856-
wix_toolset_path,
877+
&wix_toolset_path,
857878
&output_path,
858879
arguments,
859880
&(fragment_extensions.clone().into_iter().collect()),
@@ -968,9 +989,12 @@ fn generate_resource_data(settings: &Settings) -> crate::Result<ResourceMap> {
968989
if added_resources.contains(&resource_path) {
969990
continue;
970991
}
971-
972992
added_resources.push(resource_path.clone());
973993

994+
if settings.can_sign() {
995+
try_sign(&resource_path, settings)?;
996+
}
997+
974998
let resource_entry = ResourceFile {
975999
id: format!("I{}", Uuid::new_v4().as_simple()),
9761000
guid: Uuid::new_v4().to_string(),
@@ -1055,6 +1079,10 @@ fn generate_resource_data(settings: &Settings) -> crate::Result<ResourceMap> {
10551079
.to_string_lossy()
10561080
.into_owned();
10571081
if !added_resources.iter().any(|r| r.ends_with(&relative_path)) {
1082+
if settings.can_sign() {
1083+
try_sign(resource_path, settings)?;
1084+
}
1085+
10581086
dlls.push(ResourceFile {
10591087
id: format!("I{}", Uuid::new_v4().as_simple()),
10601088
guid: Uuid::new_v4().to_string(),

crates/tauri-bundler/src/bundle/windows/nsis/installer.nsi

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ ${StrLoc}
4747
!define COPYRIGHT "{{copyright}}"
4848
!define OUTFILE "{{out_file}}"
4949
!define ARCH "{{arch}}"
50-
!define PLUGINSPATH "{{additional_plugins_path}}"
50+
!define ADDITIONALPLUGINSPATH "{{additional_plugins_path}}"
5151
!define ALLOWDOWNGRADES "{{allow_downgrades}}"
5252
!define DISPLAYLANGUAGESELECTOR "{{display_language_selector}}"
5353
!define INSTALLWEBVIEW2MODE "{{install_webview2_mode}}"
@@ -85,10 +85,8 @@ VIAddVersionKey "LegalCopyright" "${COPYRIGHT}"
8585
VIAddVersionKey "FileVersion" "${VERSION}"
8686
VIAddVersionKey "ProductVersion" "${VERSION}"
8787

88-
; Plugins path, currently exists for linux only
89-
!if "${PLUGINSPATH}" != ""
90-
!addplugindir "${PLUGINSPATH}"
91-
!endif
88+
# additional plugins
89+
!addplugindir "${ADDITIONALPLUGINSPATH}"
9290

9391
; Uninstaller signing command
9492
!if "${UNINSTALLERSIGNCOMMAND}" != ""

crates/tauri-bundler/src/bundle/windows/nsis/mod.rs

Lines changed: 111 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,18 @@ const NSIS_REQUIRED_FILES: &[&str] = &[
5555
"Include/nsDialogs.nsh",
5656
"Include/WinMessages.nsh",
5757
];
58+
const NSIS_PLUGIN_FILES: &[&str] = &[
59+
"NSISdl.dll",
60+
"StartMenu.dll",
61+
"System.dll",
62+
"nsDialogs.dll",
63+
"additional/nsis_tauri_utils.dll",
64+
];
5865
#[cfg(not(target_os = "windows"))]
59-
const NSIS_REQUIRED_FILES: &[&str] = &["Plugins/x86-unicode/nsis_tauri_utils.dll"];
66+
const NSIS_REQUIRED_FILES: &[&str] = &["Plugins/x86-unicode/additional/nsis_tauri_utils.dll"];
6067

6168
const NSIS_REQUIRED_FILES_HASH: &[(&str, &str, &str, HashAlgorithm)] = &[(
62-
"Plugins/x86-unicode/nsis_tauri_utils.dll",
69+
"Plugins/x86-unicode/additional/nsis_tauri_utils.dll",
6370
NSIS_TAURI_UTILS_URL,
6471
NSIS_TAURI_UTILS_SHA1,
6572
HashAlgorithm::Sha1,
@@ -96,7 +103,10 @@ pub fn bundle_project(settings: &Settings, updater: bool) -> crate::Result<Vec<P
96103
log::warn!("NSIS directory contains mis-hashed files. Redownloading them.");
97104
for (path, url, hash, hash_algorithm) in mismatched {
98105
let data = download_and_verify(url, hash, *hash_algorithm)?;
99-
fs::write(nsis_toolset_path.join(path), data)?;
106+
let out_path = nsis_toolset_path.join(path);
107+
std::fs::create_dir_all(out_path.parent().context("output path has no parent")?)
108+
.context("failed to create file output directory")?;
109+
fs::write(out_path, data).with_context(|| format!("failed to save {path}"))?;
100110
}
101111
}
102112
}
@@ -116,6 +126,7 @@ fn get_and_extract_nsis(nsis_toolset_path: &Path, _tauri_tools_path: &Path) -> c
116126
fs::rename(_tauri_tools_path.join("nsis-3.08"), nsis_toolset_path)?;
117127
}
118128

129+
// download additional plugins
119130
let nsis_plugins = nsis_toolset_path.join("Plugins");
120131

121132
let data = download_and_verify(
@@ -124,7 +135,7 @@ fn get_and_extract_nsis(nsis_toolset_path: &Path, _tauri_tools_path: &Path) -> c
124135
HashAlgorithm::Sha1,
125136
)?;
126137

127-
let target_folder = nsis_plugins.join("x86-unicode");
138+
let target_folder = nsis_plugins.join("x86-unicode").join("additional");
128139
fs::create_dir_all(&target_folder)?;
129140
fs::write(target_folder.join("nsis_tauri_utils.dll"), data)?;
130141

@@ -156,7 +167,7 @@ fn try_add_numeric_build_number(version_str: &str) -> anyhow::Result<String> {
156167

157168
fn build_nsis_app_installer(
158169
settings: &Settings,
159-
_nsis_toolset_path: &Path,
170+
#[allow(unused_variables)] nsis_toolset_path: &Path,
160171
tauri_tools_path: &Path,
161172
updater: bool,
162173
) -> crate::Result<Vec<PathBuf>> {
@@ -180,19 +191,83 @@ fn build_nsis_app_installer(
180191
}
181192
fs::create_dir_all(&output_path)?;
182193

194+
// we make a copy of the NSIS directory if we're going to sign its DLLs
195+
// because we don't want to change the DLL hashes so the cache can reuse it
196+
let maybe_plugin_copy_path = if settings.can_sign() {
197+
// find nsis path
198+
#[cfg(target_os = "linux")]
199+
let system_nsis_toolset_path = std::env::var_os("NSIS_PATH")
200+
.map(PathBuf::from)
201+
.unwrap_or_else(|| PathBuf::from("/usr/share/nsis"));
202+
#[cfg(target_os = "macos")]
203+
let system_nsis_toolset_path = std::env::var_os("NSIS_PATH")
204+
.map(PathBuf::from)
205+
.ok_or_else(|| anyhow::anyhow!("failed to resolve NSIS path"))
206+
.or_else(|_| {
207+
let mut makensis_path =
208+
which::which("makensis").context("failed to resolve `makensis`; did you install nsis? See https://tauri.app/distribute/windows-installer/#install-nsis for more information")?;
209+
// homebrew installs it as a symlink
210+
if makensis_path.is_symlink() {
211+
// read_link might return a path relative to makensis_path so we must use join() and canonicalize
212+
makensis_path = makensis_path
213+
.parent()
214+
.context("missing makensis parent")?
215+
.join(std::fs::read_link(&makensis_path).context("failed to resolve makensis symlink")?)
216+
.canonicalize()
217+
.context("failed to resolve makensis path")?;
218+
}
219+
// file structure:
220+
// ├── bin
221+
// │ ├── makensis
222+
// ├── share
223+
// │ ├── nsis
224+
let bin_folder = makensis_path.parent().context("missing makensis parent")?;
225+
let root_folder = bin_folder.parent().context("missing makensis root")?;
226+
crate::Result::Ok(root_folder.join("share").join("nsis"))
227+
})?;
228+
#[cfg(windows)]
229+
let system_nsis_toolset_path = nsis_toolset_path.to_path_buf();
230+
231+
let plugins_path = output_path.join("Plugins");
232+
// copy system plugins (we don't want to modify system installed DLLs, and on some systems there will even be permission errors if we try)
233+
crate::utils::fs_utils::copy_dir(
234+
&system_nsis_toolset_path.join("Plugins").join("x86-unicode"),
235+
&plugins_path.join("x86-unicode"),
236+
)
237+
.context("failed to copy system NSIS Plugins folder to local copy")?;
238+
// copy our downloaded DLLs
239+
crate::utils::fs_utils::copy_dir(
240+
&nsis_toolset_path
241+
.join("Plugins")
242+
.join("x86-unicode")
243+
.join("additional"),
244+
&plugins_path.join("x86-unicode").join("additional"),
245+
)
246+
.context("failed to copy additional NSIS Plugins folder to local copy")?;
247+
Some(plugins_path)
248+
} else {
249+
// in this case plugin_copy_path can be None, we'll use the system default path
250+
None
251+
};
252+
183253
let mut data = BTreeMap::new();
184254

185255
let bundle_id = settings.bundle_identifier();
186256
let manufacturer = settings
187257
.publisher()
188258
.unwrap_or_else(|| bundle_id.split('.').nth(1).unwrap_or(bundle_id));
189259

190-
#[cfg(not(target_os = "windows"))]
191-
{
192-
let mut dir = dirs::cache_dir().unwrap();
193-
dir.extend(["tauri", "NSIS", "Plugins", "x86-unicode"]);
194-
data.insert("additional_plugins_path", to_json(dir));
195-
}
260+
let additional_plugins_path = maybe_plugin_copy_path
261+
.clone()
262+
.unwrap_or_else(|| nsis_toolset_path.join("Plugins"))
263+
.join("x86-unicode")
264+
.join("additional");
265+
266+
data.insert(
267+
"additional_plugins_path",
268+
// either our Plugins copy (when signing) or the cache/Plugins/x86-unicode path
269+
to_json(&additional_plugins_path),
270+
);
196271

197272
data.insert("arch", to_json(arch));
198273
data.insert("bundle_id", to_json(bundle_id));
@@ -526,13 +601,29 @@ fn build_nsis_app_installer(
526601
));
527602
fs::create_dir_all(nsis_installer_path.parent().unwrap())?;
528603

529-
log::info!(action = "Running"; "makensis.exe to produce {}", display_path(&nsis_installer_path));
604+
if settings.can_sign() {
605+
log::info!("Signing NSIS plugins");
606+
for dll in NSIS_PLUGIN_FILES {
607+
let path = additional_plugins_path.join(dll);
608+
if path.exists() {
609+
try_sign(&path, settings)?;
610+
} else {
611+
log::warn!("Could not find {}, skipping signing", path.display());
612+
}
613+
}
614+
}
615+
616+
log::info!(action = "Running"; "makensis to produce {}", display_path(&nsis_installer_path));
530617

531618
#[cfg(target_os = "windows")]
532-
let mut nsis_cmd = Command::new(_nsis_toolset_path.join("makensis.exe"));
619+
let mut nsis_cmd = Command::new(nsis_toolset_path.join("makensis.exe"));
533620
#[cfg(not(target_os = "windows"))]
534621
let mut nsis_cmd = Command::new("makensis");
535622

623+
if let Some(plugins_path) = &maybe_plugin_copy_path {
624+
nsis_cmd.env("NSISPLUGINS", plugins_path);
625+
}
626+
536627
nsis_cmd
537628
.args(["-INPUTCHARSET", "UTF8", "-OUTPUTCHARSET", "UTF8"])
538629
.arg(match settings.log_level() {
@@ -628,6 +719,9 @@ fn generate_resource_data(settings: &Settings) -> crate::Result<ResourcesMap> {
628719
let loader_path =
629720
dunce::simplified(&settings.project_out_directory().join("WebView2Loader.dll")).to_path_buf();
630721
if loader_path.exists() {
722+
if settings.can_sign() {
723+
try_sign(&loader_path, settings)?;
724+
}
631725
added_resources.push(loader_path.clone());
632726
resources.insert(
633727
loader_path,
@@ -650,6 +744,10 @@ fn generate_resource_data(settings: &Settings) -> crate::Result<ResourcesMap> {
650744
}
651745
added_resources.push(resource_path.clone());
652746

747+
if settings.can_sign() {
748+
try_sign(&resource_path, settings)?;
749+
}
750+
653751
let target_path = resource.target();
654752
resources.insert(
655753
resource_path,

crates/tauri-bundler/src/bundle/windows/sign.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,9 @@ pub fn sign<P: AsRef<Path>>(path: P, params: &SignParams) -> crate::Result<()> {
241241
}
242242
}
243243

244-
pub fn try_sign(file_path: &std::path::PathBuf, settings: &Settings) -> crate::Result<()> {
244+
pub fn try_sign<P: AsRef<Path>>(file_path: P, settings: &Settings) -> crate::Result<()> {
245245
if settings.can_sign() {
246-
log::info!(action = "Signing"; "{}", tauri_utils::display_path(file_path));
246+
log::info!(action = "Signing"; "{}", tauri_utils::display_path(file_path.as_ref()));
247247
sign(file_path, &settings.sign_params())?;
248248
}
249249
Ok(())

crates/tauri-bundler/src/utils/fs_utils.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,6 @@ pub fn copy_dir(from: &Path, to: &Path) -> crate::Result<()> {
111111
"{from:?} is not a Directory"
112112
)));
113113
}
114-
if to.exists() {
115-
return Err(crate::Error::GenericError(format!("{to:?} already exists")));
116-
}
117114
let parent = to.parent().expect("No data in parent");
118115
fs::create_dir_all(parent)?;
119116
for entry in walkdir::WalkDir::new(from) {
@@ -129,7 +126,7 @@ pub fn copy_dir(from: &Path, to: &Path) -> crate::Result<()> {
129126
symlink_file(&target, &dest_path)?;
130127
}
131128
} else if entry.file_type().is_dir() {
132-
fs::create_dir(dest_path)?;
129+
fs::create_dir_all(dest_path)?;
133130
} else {
134131
fs::copy(entry.path(), dest_path)?;
135132
}

examples/api/src-tauri/build.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,20 @@ fn main() {
2222
)
2323
.expect("failed to run tauri-build");
2424

25-
// workaround needed to prevent `STATUS_ENTRYPOINT_NOT_FOUND` error in tests
26-
// see https://github.com/tauri-apps/tauri/pull/4383#issuecomment-1212221864
27-
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
28-
let target_env = std::env::var("CARGO_CFG_TARGET_ENV");
29-
let is_tauri_workspace = std::env::var("__TAURI_WORKSPACE__").is_ok_and(|v| v == "true");
30-
if is_tauri_workspace && target_os == "windows" && Ok("msvc") == target_env.as_deref() {
31-
embed_manifest_for_tests();
25+
#[cfg(windows)]
26+
{
27+
// workaround needed to prevent `STATUS_ENTRYPOINT_NOT_FOUND` error in tests
28+
// see https://github.com/tauri-apps/tauri/pull/4383#issuecomment-1212221864
29+
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
30+
let target_env = std::env::var("CARGO_CFG_TARGET_ENV");
31+
let is_tauri_workspace = std::env::var("__TAURI_WORKSPACE__").is_ok_and(|v| v == "true");
32+
if is_tauri_workspace && target_os == "windows" && Ok("msvc") == target_env.as_deref() {
33+
embed_manifest_for_tests();
34+
}
3235
}
3336
}
3437

38+
#[cfg(windows)]
3539
fn embed_manifest_for_tests() {
3640
static WINDOWS_MANIFEST_FILE: &str = "windows-app-manifest.xml";
3741

0 commit comments

Comments
 (0)