Skip to content

Commit d49b606

Browse files
Add a MacOsNotarizationCredentials variant to use a keychain profile for notarytool settings and password. (#353)
* Add a `MacOsNotarizationCredentials` variant to use a keychain profile for notarytool settings and password. This allows notarytool to use an Apple ID, Team ID and app-specific password stored in the keychain, by just providing the keychain profile as an env var. This reduces the number of env vars needed, and means the password isn't in a variable as plain text. This approach is described under "Upload your app to the notarization service" here: https://developer.apple.com/documentation/security/customizing-the-notarization-workflow * Apply suggestions from code review * add change file --------- Co-authored-by: Lucas Nogueira <118899497+lucasfernog-crabnebula@users.noreply.github.com>
1 parent b337564 commit d49b606

File tree

3 files changed

+78
-54
lines changed

3 files changed

+78
-54
lines changed

.changes/notarize-keychain.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"cargo-packager": patch
3+
"@crabnebula/packager": patch
4+
---
5+
6+
Allow using notarization credentials stored on the Keychain by providing the `APPLE_KEYCHAIN_PROFILE` environment variable. See `xcrun notarytool store-credentials` for more information.

crates/packager/src/codesign/macos.rs

Lines changed: 61 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -410,72 +410,79 @@ impl NotarytoolCmdExt for Command {
410410
.arg(key_path)
411411
.arg("--issuer")
412412
.arg(issuer),
413+
MacOsNotarizationCredentials::KeychainProfile { keychain_profile } => {
414+
self.arg("--keychain-profile").arg(keychain_profile)
415+
}
413416
}
414417
}
415418
}
416419

417420
#[tracing::instrument(level = "trace")]
418421
pub fn notarize_auth() -> crate::Result<MacOsNotarizationCredentials> {
419-
match (
420-
std::env::var_os("APPLE_ID"),
421-
std::env::var_os("APPLE_PASSWORD"),
422-
std::env::var_os("APPLE_TEAM_ID"),
423-
) {
424-
(Some(apple_id), Some(password), Some(team_id)) => {
425-
Ok(MacOsNotarizationCredentials::AppleId {
426-
apple_id,
427-
password,
428-
team_id,
429-
})
430-
}
431-
_ => {
432-
match (
433-
std::env::var_os("APPLE_API_KEY"),
434-
std::env::var_os("APPLE_API_ISSUER"),
435-
std::env::var("APPLE_API_KEY_PATH"),
436-
) {
437-
(Some(key_id), Some(issuer), Ok(key_path)) => {
438-
Ok(MacOsNotarizationCredentials::ApiKey {
439-
key_id,
440-
key_path: key_path.into(),
441-
issuer,
442-
})
443-
}
444-
(Some(key_id), Some(issuer), Err(_)) => {
445-
let mut api_key_file_name = OsString::from("AuthKey_");
446-
api_key_file_name.push(&key_id);
447-
api_key_file_name.push(".p8");
448-
let mut key_path = None;
449-
450-
let mut search_paths = vec!["./private_keys".into()];
451-
if let Some(home_dir) = dirs::home_dir() {
452-
search_paths.push(home_dir.join("private_keys"));
453-
search_paths.push(home_dir.join(".private_keys"));
454-
search_paths.push(home_dir.join(".appstoreconnect/private_keys"));
455-
}
456-
457-
for folder in search_paths {
458-
if let Some(path) = find_api_key(folder, &api_key_file_name) {
459-
key_path = Some(path);
460-
break;
461-
}
462-
}
463-
464-
if let Some(key_path) = key_path {
422+
if let Some(keychain_profile) = std::env::var_os("APPLE_KEYCHAIN_PROFILE") {
423+
Ok(MacOsNotarizationCredentials::KeychainProfile { keychain_profile })
424+
} else {
425+
match (
426+
std::env::var_os("APPLE_ID"),
427+
std::env::var_os("APPLE_PASSWORD"),
428+
std::env::var_os("APPLE_TEAM_ID"),
429+
) {
430+
(Some(apple_id), Some(password), Some(team_id)) => {
431+
Ok(MacOsNotarizationCredentials::AppleId {
432+
apple_id,
433+
password,
434+
team_id,
435+
})
436+
}
437+
_ => {
438+
match (
439+
std::env::var_os("APPLE_API_KEY"),
440+
std::env::var_os("APPLE_API_ISSUER"),
441+
std::env::var("APPLE_API_KEY_PATH"),
442+
) {
443+
(Some(key_id), Some(issuer), Ok(key_path)) => {
465444
Ok(MacOsNotarizationCredentials::ApiKey {
466445
key_id,
467-
key_path,
446+
key_path: key_path.into(),
468447
issuer,
469448
})
470-
} else {
471-
Err(Error::ApiKeyMissing {
472-
filename: api_key_file_name
473-
.into_string()
474-
.expect("failed to convert api_key_file_name to string"),
475-
})
476449
}
450+
(Some(key_id), Some(issuer), Err(_)) => {
451+
let mut api_key_file_name = OsString::from("AuthKey_");
452+
api_key_file_name.push(&key_id);
453+
api_key_file_name.push(".p8");
454+
let mut key_path = None;
455+
456+
let mut search_paths = vec!["./private_keys".into()];
457+
if let Some(home_dir) = dirs::home_dir() {
458+
search_paths.push(home_dir.join("private_keys"));
459+
search_paths.push(home_dir.join(".private_keys"));
460+
search_paths.push(home_dir.join(".appstoreconnect/private_keys"));
461+
}
462+
463+
for folder in search_paths {
464+
if let Some(path) = find_api_key(folder, &api_key_file_name) {
465+
key_path = Some(path);
466+
break;
467+
}
468+
}
469+
470+
if let Some(key_path) = key_path {
471+
Ok(MacOsNotarizationCredentials::ApiKey {
472+
key_id,
473+
key_path,
474+
issuer,
475+
})
476+
} else {
477+
Err(Error::ApiKeyMissing {
478+
filename: api_key_file_name
479+
.into_string()
480+
.expect("failed to convert api_key_file_name to string"),
481+
})
482+
}
483+
}
484+
_ => Err(Error::MissingNotarizeAuthVars),
477485
}
478-
_ => Err(Error::MissingNotarizeAuthVars),
479486
}
480487
}
481488
}

crates/packager/src/config/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,17 @@ pub enum MacOsNotarizationCredentials {
680680
/// Path to the API key file.
681681
key_path: PathBuf,
682682
},
683+
/// Keychain profile with a stored app-specific password for notarytool to use
684+
/// Passwords can be generated at https://account.apple.com when signed in with your developer account.
685+
/// The password must then be stored in your keychain for notarytool to access,
686+
/// using the following, with the appopriate Apple and Team IDs:
687+
/// `xcrun notarytool store-credentials --apple-id "name@example.com" --team-id "ABCD123456"`
688+
/// This will prompt for a keychain profile name, and the password itself.
689+
/// This setting can only be provided as an environment variable "APPLE_KEYCHAIN_PROFILE"
690+
KeychainProfile {
691+
/// The keychain profile name (as provided when the password was stored using notarytool)
692+
keychain_profile: OsString,
693+
},
683694
}
684695

685696
/// The macOS configuration.

0 commit comments

Comments
 (0)