Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add config.windows.sign_command to override signing command #94

Merged
merged 3 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading