Skip to content

Commit

Permalink
feat: add config.windows.sign_command to override signing command (#94
Browse files Browse the repository at this point in the history
)

* feat: add `config.windows.sign_command` to override signing command

closes #81

* fix build

* clippy
  • Loading branch information
amr-crabnebula authored Dec 11, 2023
1 parent 2d4c34d commit 6967837
Show file tree
Hide file tree
Showing 11 changed files with 1,367 additions and 101 deletions.
1,202 changes: 1,202 additions & 0 deletions bindings/packager/nodejs/schema.json

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions crates/packager/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,13 @@
"description": "Whether to validate a second app installation, blocking the user from installing an older version if set to `false`.\n\nFor instance, if `1.2.1` is installed, the user won't be able to install app version `1.2.0` or `1.1.5`.\n\nThe default value of this flag is `true`.",
"default": true,
"type": "boolean"
},
"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\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
Expand Down
2 changes: 1 addition & 1 deletion crates/packager/src/codesign/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ fn sign(
pcakger_keychain: bool,
) -> crate::Result<()> {
tracing::info!(
"Signing {} with identity \"{}\"",
"Codesigning {} with identity \"{}\"",
path_to_sign.display(),
identity
);
Expand Down
10 changes: 2 additions & 8 deletions crates/packager/src/codesign/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,6 @@
// SPDX-License-Identifier: MIT

#[cfg(target_os = "macos")]
#[path = "macos.rs"]
mod imp;
pub mod macos;

#[cfg(windows)]
#[path = "windows.rs"]
mod imp;

#[cfg(any(windows, target_os = "macos"))]
pub use imp::*;
pub mod windows;
195 changes: 125 additions & 70 deletions crates/packager/src/codesign/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,22 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

#![cfg(windows)]

use std::{
fmt::Debug,
path::{Path, PathBuf},
process::Command,
};
use std::{fmt::Debug, path::Path, process::Command};

#[cfg(windows)]
use once_cell::sync::Lazy;
#[cfg(windows)]
use std::path::PathBuf;
#[cfg(windows)]
use winreg::{
enums::{HKEY_LOCAL_MACHINE, KEY_READ, KEY_WOW64_32KEY},
RegKey,
};

use crate::{
config::Config,
shell::CommandExt,
util::{self, display_path, Bitness},
};
use crate::{config::Config, shell::CommandExt, util};

#[cfg(windows)]
use crate::util::Bitness;

#[derive(Debug)]
pub struct SignParams {
Expand All @@ -30,8 +27,49 @@ pub struct SignParams {
pub certificate_thumbprint: String,
pub timestamp_url: Option<String>,
pub tsp: bool,
pub sign_command: Option<String>,
}

pub(crate) trait ConfigSignExt {
fn can_sign(&self) -> bool;
fn custom_sign_command(&self) -> bool;
fn sign_params(&self) -> SignParams;
}

impl ConfigSignExt for Config {
fn can_sign(&self) -> bool {
self.windows()
.and_then(|w| w.certificate_thumbprint.as_ref())
.is_some()
|| self.custom_sign_command()
}

fn custom_sign_command(&self) -> bool {
self.windows()
.and_then(|w| w.sign_command.as_ref())
.is_some()
}

fn sign_params(&self) -> SignParams {
let windows = self.windows();
SignParams {
product_name: self.product_name.clone(),
digest_algorithm: windows
.and_then(|w| w.digest_algorithm.as_ref())
.cloned()
.unwrap_or_else(|| "sha256".to_string()),
certificate_thumbprint: windows
.and_then(|w| w.certificate_thumbprint.as_ref())
.cloned()
.unwrap_or_default(),
timestamp_url: windows.and_then(|w| w.timestamp_url.as_ref()).cloned(),
tsp: windows.map(|w| w.tsp).unwrap_or_default(),
sign_command: windows.and_then(|w| w.sign_command.as_ref()).cloned(),
}
}
}

#[cfg(windows)]
static SIGN_TOOL: Lazy<crate::Result<PathBuf>> = Lazy::new(|| {
let _s = tracing::span!(tracing::Level::TRACE, "locate_signtool");
const INSTALLED_ROOTS_REGKEY_PATH: &str = r"SOFTWARE\Microsoft\Windows Kits\Installed Roots";
Expand Down Expand Up @@ -94,12 +132,31 @@ static SIGN_TOOL: Lazy<crate::Result<PathBuf>> = Lazy::new(|| {
Err(crate::Error::SignToolNotFound)
});

#[cfg(windows)]
fn signtool() -> Option<PathBuf> {
(*SIGN_TOOL).as_ref().ok().cloned()
}

#[tracing::instrument(level = "trace")]
pub fn sign_command<P: AsRef<Path> + Debug>(
pub fn sign_command_custom<P: AsRef<Path> + Debug>(
path: P,
command: &str,
) -> crate::Result<Command> {
let custom_command = command.replace("%1", &dunce::simplified(path.as_ref()).to_string_lossy());

let mut args = custom_command.split(' ');
let bin = args
.next()
.expect("custom signing command doesn't contain a bin?");

let mut cmd = Command::new(bin);
cmd.args(args);
Ok(cmd)
}

#[cfg(windows)]
#[tracing::instrument(level = "trace")]
pub fn sign_command_default<P: AsRef<Path> + Debug>(
path: P,
params: &SignParams,
) -> crate::Result<Command> {
Expand All @@ -125,88 +182,86 @@ pub fn sign_command<P: AsRef<Path> + Debug>(
Ok(cmd)
}

pub(crate) trait ConfigSignExt {
fn can_sign(&self) -> bool;
fn sign_params(&self) -> SignParams;
}
#[tracing::instrument(level = "trace")]
pub fn sign_command<P: AsRef<Path> + Debug>(
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),

impl ConfigSignExt for Config {
fn can_sign(&self) -> bool {
self.windows()
.and_then(|w| w.certificate_thumbprint.as_ref())
.is_some()
// should not be reachable
#[cfg(not(windows))]
None => Ok(Command::new("")),
}
}

fn sign_params(&self) -> SignParams {
let windows = self.windows();
SignParams {
product_name: self.product_name.clone(),
digest_algorithm: windows
.and_then(|w| w.digest_algorithm.as_ref())
.as_ref()
.map(|algorithm| algorithm.to_string())
.unwrap_or_else(|| "sha256".to_string()),
certificate_thumbprint: windows
.and_then(|w| w.certificate_thumbprint.as_ref())
.cloned()
.unwrap_or_default(),
timestamp_url: windows
.and_then(|w| w.timestamp_url.as_ref())
.as_ref()
.map(|url| url.to_string()),
tsp: windows.map(|w| w.tsp).unwrap_or_default(),
}
}
#[tracing::instrument(level = "trace")]
pub fn sign_custom<P: AsRef<Path> + Debug>(path: P, custom_command: &str) -> crate::Result<()> {
let path = path.as_ref();

tracing::info!(
"Codesigning {} with a custom signing command",
util::display_path(path),
);

let mut cmd = sign_command_custom(path, custom_command)?;

let output = cmd
.output_ok()
.map_err(crate::Error::CustomSignCommandFailed)?;

let stdout = String::from_utf8_lossy(output.stdout.as_slice());
tracing::info!("{:?}", stdout);

Ok(())
}

#[tracing::instrument(level = "trace")]
pub fn sign<P: AsRef<Path> + Debug>(path: P, params: &SignParams) -> crate::Result<()> {
#[cfg(windows)]
pub fn sign_default<P: AsRef<Path> + Debug>(path: P, params: &SignParams) -> crate::Result<()> {
let signtool = signtool().ok_or(crate::Error::SignToolNotFound)?;
let path = path.as_ref();

tracing::info!(
"Signing {} with identity \"{}\"",
display_path(path),
"Codesigning {} with identity \"{}\"",
util::display_path(path),
params.certificate_thumbprint
);

let mut cmd = sign_command_default(path, params)?;

tracing::debug!("Running signtool {:?}", signtool);
let mut cmd = sign_command(path, params)?;
let output = cmd.output_ok().map_err(crate::Error::SignToolFailed)?;
let stdout = String::from_utf8_lossy(output.stdout.as_slice()).into_owned();

let stdout = String::from_utf8_lossy(output.stdout.as_slice());
tracing::info!("{:?}", stdout);

Ok(())
}

#[tracing::instrument(level = "trace")]
pub fn sign<P: AsRef<Path> + Debug>(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
#[cfg(not(windows))]
None => Ok(()),
}
}

#[tracing::instrument(level = "trace")]
pub fn try_sign(
file_path: &std::path::PathBuf,
config: &crate::config::Config,
) -> crate::Result<()> {
let windows_config = config.windows();
if let Some(certificate_thumbprint) =
windows_config.and_then(|c| c.certificate_thumbprint.as_ref())
{
tracing::info!("Signing {}", util::display_path(file_path));
sign(
file_path,
&SignParams {
product_name: config.product_name.clone(),
digest_algorithm: windows_config
.and_then(|c| {
c.digest_algorithm
.as_ref()
.map(|algorithm| algorithm.to_string())
})
.unwrap_or_else(|| "sha256".to_string()),
certificate_thumbprint: certificate_thumbprint.to_string(),
timestamp_url: windows_config
.and_then(|c| c.timestamp_url.as_ref())
.map(|url| url.to_string()),
tsp: windows_config.map(|c| c.tsp).unwrap_or_default(),
},
)?;
if config.can_sign() {
sign(file_path, &config.sign_params())?;
}
Ok(())
}
11 changes: 11 additions & 0 deletions crates/packager/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1225,6 +1225,16 @@ pub struct WindowsConfig {
alias = "allow_downgrades"
)]
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.
///
/// 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", alias = "sign_command")]
pub sign_command: Option<String>,
}

impl Default for WindowsConfig {
Expand All @@ -1235,6 +1245,7 @@ impl Default for WindowsConfig {
timestamp_url: None,
tsp: false,
allow_downgrades: true,
sign_command: None,
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion crates/packager/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,12 @@ pub enum Error {
/// create-dmg script error
#[error("Error running create-dmg script: {0}")]
CreateDmgFailed(std::io::Error),
/// create-dmg script error
/// signtool.exe error
#[error("Error running signtool.exe: {0}")]
SignToolFailed(std::io::Error),
/// Custom signing command error
#[error("Error running custom signing command: {0}")]
CustomSignCommandFailed(std::io::Error),
/// bundle_appimage script error
#[error("Error running bundle_appimage.sh script: {0}")]
AppImageScriptFailed(std::io::Error),
Expand Down
2 changes: 1 addition & 1 deletion crates/packager/src/package/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use std::{

use super::Context;
use crate::{
codesign::{self, SignTarget},
codesign::macos::{self as codesign, SignTarget},
config::Config,
shell::CommandExt,
util,
Expand Down
2 changes: 1 addition & 1 deletion crates/packager/src/package/dmg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::{os::unix::fs::PermissionsExt, path::PathBuf, process::Command};

use super::Context;
use crate::{
codesign,
codesign::macos as codesign,
shell::CommandExt,
util::{self, download},
};
Expand Down
Loading

0 comments on commit 6967837

Please sign in to comment.