Browse Source

feat(bundler/nsis): sign uninstaller, closes #7348 (#7398)

* feat(bundler/nsis): sign uninstaller, closes #7348

* Update bundler-nsis-sign-uninstaller.md

* clippy
Amr Bashir 2 years ago
parent
commit
764968ab38

+ 5 - 0
.changes/bundler-nsis-sign-uninstaller.md

@@ -0,0 +1,5 @@
+---
+'tauri-bundler': 'minor:enhance'
+---
+
+Sign NSIS uninstaller as well.

+ 7 - 4
tooling/bundler/src/bundle/windows/msi/wix.rs

@@ -7,10 +7,13 @@ use crate::bundle::{
   common::CommandExt,
   path_utils::{copy_file, FileOpts},
   settings::Settings,
-  windows::util::{
-    download, download_and_verify, extract_zip, try_sign, HashAlgorithm, WEBVIEW2_BOOTSTRAPPER_URL,
-    WEBVIEW2_X64_INSTALLER_GUID, WEBVIEW2_X86_INSTALLER_GUID, WIX_OUTPUT_FOLDER_NAME,
-    WIX_UPDATER_OUTPUT_FOLDER_NAME,
+  windows::{
+    sign::try_sign,
+    util::{
+      download, download_and_verify, extract_zip, HashAlgorithm, WEBVIEW2_BOOTSTRAPPER_URL,
+      WEBVIEW2_X64_INSTALLER_GUID, WEBVIEW2_X86_INSTALLER_GUID, WIX_OUTPUT_FOLDER_NAME,
+      WIX_UPDATER_OUTPUT_FOLDER_NAME,
+    },
   },
 };
 use anyhow::{bail, Context};

+ 14 - 1
tooling/bundler/src/bundle/windows/nsis.rs

@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: MIT
 
 #[cfg(target_os = "windows")]
-use crate::bundle::windows::util::try_sign;
+use crate::bundle::windows::sign::{sign_command, try_sign};
 use crate::{
   bundle::{
     common::CommandExt,
@@ -160,6 +160,7 @@ fn build_nsis_app_installer(
 
   info!("Target: {}", arch);
 
+  // Code signing is currently only supported on Windows hosts
   #[cfg(target_os = "windows")]
   {
     let main_binary = settings
@@ -201,6 +202,18 @@ fn build_nsis_app_installer(
   data.insert("short_description", to_json(settings.short_description()));
   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 version = settings.version_string();
   data.insert("version", to_json(version));
   data.insert(

+ 51 - 11
tooling/bundler/src/bundle/windows/sign.rs

@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use crate::bundle::common::CommandExt;
+use crate::{bundle::common::CommandExt, Settings};
 use bitness::{self, Bitness};
 use log::{debug, info};
 use std::{
@@ -90,18 +90,11 @@ fn locate_signtool() -> crate::Result<PathBuf> {
   Err(crate::Error::SignToolNotFound)
 }
 
-pub fn sign<P: AsRef<Path>>(path: P, params: &SignParams) -> crate::Result<()> {
-  // Convert path to string reference, as we need to pass it as a command-line parameter to signtool
-  let path_str = path.as_ref().to_str().unwrap();
-
-  info!(action = "Signing"; "{} with identity \"{}\"", path_str, params.certificate_thumbprint);
-
+pub fn sign_command(path: &str, params: &SignParams) -> crate::Result<(Command, PathBuf)> {
   // Construct SignTool command
   let signtool = locate_signtool()?;
 
-  debug!("Running signtool {:?}", signtool);
-
-  let mut cmd = Command::new(signtool);
+  let mut cmd = Command::new(&signtool);
   cmd.arg("sign");
   cmd.args(["/fd", &params.digest_algorithm]);
   cmd.args(["/sha1", &params.certificate_thumbprint]);
@@ -116,7 +109,18 @@ pub fn sign<P: AsRef<Path>>(path: P, params: &SignParams) -> crate::Result<()> {
     }
   }
 
-  cmd.arg(path_str);
+  cmd.arg(path);
+
+  Ok((cmd, signtool))
+}
+
+pub fn sign<P: AsRef<Path>>(path: P, params: &SignParams) -> crate::Result<()> {
+  let path_str = path.as_ref().to_str().unwrap();
+
+  info!(action = "Signing"; "{} with identity \"{}\"", path_str, params.certificate_thumbprint);
+
+  let (mut cmd, signtool) = sign_command(path_str, params)?;
+  debug!("Running signtool {:?}", signtool);
 
   // Execute SignTool command
   let output = cmd.output_ok()?;
@@ -126,3 +130,39 @@ 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 try_sign(file_path: &std::path::PathBuf, settings: &Settings) -> crate::Result<()> {
+  if settings.can_sign() {
+    info!(action = "Signing"; "{}", tauri_utils::display_path(file_path));
+    sign(file_path, &settings.sign_params())?;
+  }
+  Ok(())
+}

+ 5 - 0
tooling/bundler/src/bundle/windows/templates/installer.nsi

@@ -34,6 +34,7 @@ ${StrLoc}
 !define WEBVIEW2INSTALLERPATH "{{webview2_installer_path}}"
 !define UNINSTKEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCTNAME}"
 !define MANUPRODUCTKEY "Software\${MANUFACTURER}\${PRODUCTNAME}"
+!define UNINSTALLERSIGNCOMMAND "{{uninstaller_sign_cmd}}"
 
 Name "${PRODUCTNAME}"
 BrandingText "${COPYRIGHT}"
@@ -51,6 +52,10 @@ VIAddVersionKey "ProductVersion" "${VERSION}"
     !addplugindir "${PLUGINSPATH}"
 !endif
 
+!if "${UNINSTALLERSIGNCOMMAND}" != ""
+  !uninstfinalize '${UNINSTALLERSIGNCOMMAND}'
+!endif
+
 ; Handle install mode, `perUser`, `perMachine` or `both`
 !if "${INSTALLMODE}" == "perMachine"
   RequestExecutionLevel highest

+ 0 - 34
tooling/bundler/src/bundle/windows/util.rs

@@ -12,11 +12,6 @@ use log::info;
 use sha2::Digest;
 use zip::ZipArchive;
 
-#[cfg(target_os = "windows")]
-use crate::bundle::windows::sign::{sign, SignParams};
-#[cfg(target_os = "windows")]
-use crate::Settings;
-
 pub const WEBVIEW2_BOOTSTRAPPER_URL: &str = "https://go.microsoft.com/fwlink/p/?LinkId=2124703";
 pub const WEBVIEW2_X86_INSTALLER_GUID: &str = "a17bde80-b5ab-47b5-8bbb-1cbe93fc6ec9";
 pub const WEBVIEW2_X64_INSTALLER_GUID: &str = "aa5fd9b3-dc11-4cbc-8343-a50f57b311e1";
@@ -75,35 +70,6 @@ fn verify(data: &Vec<u8>, hash: &str, mut hasher: impl Digest) -> crate::Result<
   }
 }
 
-#[cfg(target_os = "windows")]
-pub fn try_sign(file_path: &std::path::PathBuf, settings: &Settings) -> crate::Result<()> {
-  use tauri_utils::display_path;
-
-  if let Some(certificate_thumbprint) = settings.windows().certificate_thumbprint.as_ref() {
-    info!(action = "Signing"; "{}", display_path(file_path));
-    sign(
-      file_path,
-      &SignParams {
-        product_name: settings.product_name().into(),
-        digest_algorithm: settings
-          .windows()
-          .digest_algorithm
-          .as_ref()
-          .map(|algorithm| algorithm.to_string())
-          .unwrap_or_else(|| "sha256".to_string()),
-        certificate_thumbprint: certificate_thumbprint.to_string(),
-        timestamp_url: settings
-          .windows()
-          .timestamp_url
-          .as_ref()
-          .map(|url| url.to_string()),
-        tsp: settings.windows().tsp,
-      },
-    )?;
-  }
-  Ok(())
-}
-
 /// Extracts the zips from memory into a useable path.
 pub fn extract_zip(data: &[u8], path: &Path) -> crate::Result<()> {
   let cursor = Cursor::new(data);