diff --git a/cargo-dist/src/backend/installer/macpkg.rs b/cargo-dist/src/backend/installer/macpkg.rs index e01158601..debbd5a04 100644 --- a/cargo-dist/src/backend/installer/macpkg.rs +++ b/cargo-dist/src/backend/installer/macpkg.rs @@ -7,9 +7,13 @@ use axoprocess::Cmd; use camino::Utf8PathBuf; use serde::Serialize; use temp_dir::TempDir; -use tracing::info; +use tracing::{info, warn}; -use crate::{create_tmp, DistResult}; +use crate::{ + create_tmp, + sign::macos::{Codesign, Keychain}, + DistResult, TargetTriple, +}; use super::ExecutableZipFragment; @@ -30,6 +34,15 @@ pub struct PkgInstallerInfo { pub version: String, /// Executable aliases pub bin_aliases: BTreeMap>, + /// Whether to sign package artifacts + pub macos_sign: bool, + /// The target we're building from + pub host_target: TargetTriple, +} + +struct SigningEnv { + pub keychain: Keychain, + pub identity: String, } impl PkgInstallerInfo { @@ -37,6 +50,20 @@ impl PkgInstallerInfo { pub fn build(&self) -> DistResult<()> { info!("building a pkg: {}", self.identifier); + let signing_env = if self.macos_sign { + if let Some(signer) = Codesign::new(&self.host_target)? { + Some(SigningEnv { + keychain: signer.create_keychain()?, + identity: signer.identity().to_owned(), + }) + } else { + warn!("Signing was requested, but we weren't able to construct the signing environment - config may be missing"); + None + } + } else { + None + }; + // We can't build directly from dist_dir because the // package installer wants the directory we feed it // to have the final package layout, which in this case @@ -81,6 +108,14 @@ impl PkgInstallerInfo { pkgcmd.arg("--install-location").arg(&self.install_location); pkgcmd.arg("--version").arg(&self.version); pkgcmd.arg(&pkg_path); + + // If we've been asked to sign, and we have the required + // environment, do that here. + if let Some(signing_env) = &signing_env { + pkgcmd.arg("--sign").arg(&signing_env.identity); + pkgcmd.arg("--keychain").arg(&signing_env.keychain.path); + } + // Ensures stdout from the build process doesn't taint the dist-manifest pkgcmd.stdout_to_stderr(); pkgcmd.run()?; @@ -89,6 +124,13 @@ impl PkgInstallerInfo { let mut productcmd = Cmd::new("/usr/bin/productbuild", "create final product .pkg"); productcmd.arg("--package").arg(&pkg_path); productcmd.arg(&product_path); + + // We also need to sign the product .pkg. + if let Some(signing_env) = &signing_env { + productcmd.arg("--sign").arg(&signing_env.identity); + productcmd.arg("--keychain").arg(&signing_env.keychain.path); + } + productcmd.stdout_to_stderr(); productcmd.run()?; diff --git a/cargo-dist/src/sign/macos.rs b/cargo-dist/src/sign/macos.rs index d9a0b1b15..abc5d5663 100644 --- a/cargo-dist/src/sign/macos.rs +++ b/cargo-dist/src/sign/macos.rs @@ -27,10 +27,12 @@ use tracing::warn; use crate::{create_tmp, DistError, DistResult, TargetTriple}; -struct Keychain { +/// Represents a temporary macOS keychain database. The database object will be created within `_root`, and deleted once this struct is dropped. +pub struct Keychain { _root: TempDir, root_path: Utf8PathBuf, password: String, + /// The path to the keychain database. pub path: Utf8PathBuf, } @@ -156,6 +158,7 @@ impl std::fmt::Debug for CodesignEnv { } impl Codesign { + /// Creates a new Codesign instance, if the host is darwin and the required information is in the environment pub fn new(host_target: &TargetTriple) -> DistResult> { if !host_target.contains("darwin") { return Ok(None); @@ -182,11 +185,20 @@ impl Codesign { val } - pub fn sign(&self, file: &Utf8Path) -> DistResult<()> { + /// Creates a Keychain with this signer's certificate imported, + /// then returns it. + pub fn create_keychain(&self) -> DistResult { let password = uuid::Uuid::new_v4().as_hyphenated().to_string(); let keychain = Keychain::create(password)?; keychain.import_certificate(&self.env.certificate, &self.env.password)?; + Ok(keychain) + } + + /// Signs a binary using `codesign`. + pub fn sign(&self, file: &Utf8Path) -> DistResult<()> { + let keychain = self.create_keychain()?; + let mut cmd = Cmd::new("/usr/bin/codesign", "sign macOS artifacts"); cmd.arg("--sign").arg(&self.env.identity); cmd.arg("--keychain").arg(&keychain.path); @@ -196,4 +208,9 @@ impl Codesign { Ok(()) } + + /// Returns the signing identity represented by this signer. + pub fn identity(&self) -> &str { + &self.env.identity + } } diff --git a/cargo-dist/src/sign/mod.rs b/cargo-dist/src/sign/mod.rs index 4bf152ef6..155cd8cbe 100644 --- a/cargo-dist/src/sign/mod.rs +++ b/cargo-dist/src/sign/mod.rs @@ -8,7 +8,7 @@ use camino::Utf8Path; use crate::{config::ProductionMode, DistResult, TargetTriple}; -mod macos; +pub mod macos; mod ssldotcom; /// Code/artifact signing providers diff --git a/cargo-dist/src/tasks.rs b/cargo-dist/src/tasks.rs index 66c36fc82..a57308163 100644 --- a/cargo-dist/src/tasks.rs +++ b/cargo-dist/src/tasks.rs @@ -2274,6 +2274,8 @@ impl<'pkg_graph> DistGraphBuilder<'pkg_graph> { }; let bin_aliases = bin_aliases.for_target(&variant.target); + let macos_sign = self.inner.config.builds.macos_sign; + let host_target = self.inner.tools.cargo.host_target.clone(); // Gather up the bundles the installer supports let installer_artifact = Artifact { @@ -2297,6 +2299,8 @@ impl<'pkg_graph> DistGraphBuilder<'pkg_graph> { install_location: config.install_location.clone(), version: version.to_string(), bin_aliases, + macos_sign, + host_target, })), is_global: false, };