diff --git a/.changes/codesign-notarize-auth-config.md b/.changes/codesign-notarize-auth-config.md new file mode 100644 index 00000000..7f6b0754 --- /dev/null +++ b/.changes/codesign-notarize-auth-config.md @@ -0,0 +1,5 @@ +--- +"cargo-packager": minor +--- + +Added codesign certificate and notarization credentials configuration options under the `macos` config (for programatic usage, taking precedence over environment variables). diff --git a/Cargo.lock b/Cargo.lock index b73a417e..14ecc0c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -978,7 +978,7 @@ dependencies = [ [[package]] name = "cargo-packager" -version = "0.6.0" +version = "0.6.1" dependencies = [ "ar", "base64 0.21.7", diff --git a/crates/packager/src/codesign/macos.rs b/crates/packager/src/codesign/macos.rs index a474f7ba..105ae835 100644 --- a/crates/packager/src/codesign/macos.rs +++ b/crates/packager/src/codesign/macos.rs @@ -14,7 +14,7 @@ use std::{ use serde::Deserialize; -use crate::{shell::CommandExt, Config, Error}; +use crate::{config::MacOsNotarizationCredentials, shell::CommandExt, Config, Error}; const KEYCHAIN_ID: &str = "cargo-packager.keychain"; const KEYCHAIN_PWD: &str = "cargo-packager"; @@ -171,10 +171,19 @@ impl PartialOrd for SignTarget { #[tracing::instrument(level = "trace")] pub fn try_sign(targets: Vec, identity: &str, config: &Config) -> crate::Result<()> { - let packager_keychain = if let (Some(certificate_encoded), Some(certificate_password)) = ( - std::env::var_os("APPLE_CERTIFICATE"), - std::env::var_os("APPLE_CERTIFICATE_PASSWORD"), - ) { + let certificate_encoded = config + .macos() + .map(|m| m.signing_certificate.clone()) + .unwrap_or_else(|| std::env::var_os("APPLE_CERTIFICATE")); + + let certificate_password = config + .macos() + .map(|m| m.signing_certificate_password.clone()) + .unwrap_or_else(|| std::env::var_os("APPLE_CERTIFICATE_PASSWORD")); + + let packager_keychain = if let (Some(certificate_encoded), Some(certificate_password)) = + (certificate_encoded, certificate_password) + { // setup keychain allow you to import your certificate // for CI build setup_keychain(certificate_encoded, certificate_password)?; @@ -253,7 +262,7 @@ struct NotarytoolSubmitOutput { #[tracing::instrument(level = "trace")] pub fn notarize( app_bundle_path: PathBuf, - auth: NotarizeAuth, + auth: MacOsNotarizationCredentials, config: &Config, ) -> crate::Result<()> { let bundle_stem = app_bundle_path @@ -361,28 +370,14 @@ fn staple_app(app_bundle_path: PathBuf) -> crate::Result<()> { Ok(()) } -#[derive(Debug)] -pub enum NotarizeAuth { - AppleId { - apple_id: OsString, - password: OsString, - team_id: OsString, - }, - ApiKey { - key: OsString, - key_path: PathBuf, - issuer: OsString, - }, -} - pub trait NotarytoolCmdExt { - fn notarytool_args(&mut self, auth: &NotarizeAuth) -> &mut Self; + fn notarytool_args(&mut self, auth: &MacOsNotarizationCredentials) -> &mut Self; } impl NotarytoolCmdExt for Command { - fn notarytool_args(&mut self, auth: &NotarizeAuth) -> &mut Self { + fn notarytool_args(&mut self, auth: &MacOsNotarizationCredentials) -> &mut Self { match auth { - NotarizeAuth::AppleId { + MacOsNotarizationCredentials::AppleId { apple_id, password, team_id, @@ -396,13 +391,13 @@ impl NotarytoolCmdExt for Command { self } - NotarizeAuth::ApiKey { - key, + MacOsNotarizationCredentials::ApiKey { + key_id, key_path, issuer, } => self .arg("--key-id") - .arg(key) + .arg(key_id) .arg("--key") .arg(key_path) .arg("--issuer") @@ -412,31 +407,35 @@ impl NotarytoolCmdExt for Command { } #[tracing::instrument(level = "trace")] -pub fn notarize_auth() -> crate::Result { +pub fn notarize_auth() -> crate::Result { match ( std::env::var_os("APPLE_ID"), std::env::var_os("APPLE_PASSWORD"), std::env::var_os("APPLE_TEAM_ID"), ) { - (Some(apple_id), Some(password), Some(team_id)) => Ok(NotarizeAuth::AppleId { - apple_id, - password, - team_id, - }), + (Some(apple_id), Some(password), Some(team_id)) => { + Ok(MacOsNotarizationCredentials::AppleId { + apple_id, + password, + team_id, + }) + } _ => { match ( std::env::var_os("APPLE_API_KEY"), std::env::var_os("APPLE_API_ISSUER"), std::env::var("APPLE_API_KEY_PATH"), ) { - (Some(key), Some(issuer), Ok(key_path)) => Ok(NotarizeAuth::ApiKey { - key, - key_path: key_path.into(), - issuer, - }), - (Some(key), Some(issuer), Err(_)) => { + (Some(key_id), Some(issuer), Ok(key_path)) => { + Ok(MacOsNotarizationCredentials::ApiKey { + key_id, + key_path: key_path.into(), + issuer, + }) + } + (Some(key_id), Some(issuer), Err(_)) => { let mut api_key_file_name = OsString::from("AuthKey_"); - api_key_file_name.push(&key); + api_key_file_name.push(&key_id); api_key_file_name.push(".p8"); let mut key_path = None; @@ -455,8 +454,8 @@ pub fn notarize_auth() -> crate::Result { } if let Some(key_path) = key_path { - Ok(NotarizeAuth::ApiKey { - key, + Ok(MacOsNotarizationCredentials::ApiKey { + key_id, key_path, issuer, }) diff --git a/crates/packager/src/config/mod.rs b/crates/packager/src/config/mod.rs index ab62ee40..356db61f 100644 --- a/crates/packager/src/config/mod.rs +++ b/crates/packager/src/config/mod.rs @@ -6,6 +6,7 @@ use std::{ collections::HashMap, + ffi::OsString, fmt::{self, Display}, path::{Path, PathBuf}, }; @@ -530,6 +531,29 @@ impl DmgConfig { } } +/// Notarization authentication credentials. +#[derive(Clone, Debug)] +pub enum MacOsNotarizationCredentials { + /// Apple ID authentication. + AppleId { + /// Apple ID. + apple_id: OsString, + /// Password. + password: OsString, + /// Team ID. + team_id: OsString, + }, + /// App Store Connect API key. + ApiKey { + /// API key issuer. + issuer: OsString, + /// API key ID. + key_id: OsString, + /// Path to the API key file. + key_path: PathBuf, + }, +} + /// The macOS configuration. #[derive(Clone, Debug, Default, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] @@ -559,6 +583,15 @@ pub struct MacOsConfig { /// Code signing identity. #[serde(alias = "signing-identity", alias = "signing_identity")] pub signing_identity: Option, + /// Codesign certificate (base64 encoded of the p12 file). + #[serde(skip)] + pub signing_certificate: Option, + /// Password of the codesign certificate. + #[serde(skip)] + pub signing_certificate_password: Option, + /// Notarization authentication credentials. + #[serde(skip)] + pub notarization_credentials: Option, /// Provider short name for notarization. #[serde(alias = "provider-short-name", alias = "provider_short_name")] pub provider_short_name: Option, diff --git a/crates/packager/src/package/app/mod.rs b/crates/packager/src/package/app/mod.rs index 0210498a..4d0f4ea0 100644 --- a/crates/packager/src/package/app/mod.rs +++ b/crates/packager/src/package/app/mod.rs @@ -151,7 +151,15 @@ pub(crate) fn package(ctx: &Context) -> crate::Result> { codesign::try_sign(sign_paths, identity, config)?; // notarization is required for distribution - match codesign::notarize_auth() { + match config + .macos() + .map(|m| { + m.notarization_credentials + .clone() + .ok_or_else(|| crate::Error::MissingNotarizeAuthVars) + }) + .unwrap_or_else(|| codesign::notarize_auth()) + { Ok(auth) => { tracing::debug!("Notarizing {}", app_bundle_path.display()); codesign::notarize(app_bundle_path.clone(), auth, config)?;