electron-cli 0.3.0-alpha.17 → 0.3.0-alpha.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/README.md +5 -4
- package/package.json +1 -1
- package/src/commands/package.rs +341 -18
package/Cargo.lock
CHANGED
package/Cargo.toml
CHANGED
package/README.md
CHANGED
|
@@ -41,9 +41,9 @@ The Rust-native flow currently owns:
|
|
|
41
41
|
|
|
42
42
|
The GitHub publisher creates or reuses a release, uploads selected make artifacts, and can replace an existing asset with `--force`. It reads `GITHUB_TOKEN` or `GH_TOKEN` and can infer `OWNER/REPO` from package metadata, Forge GitHub publisher config, or `package.json` `repository`. You can also pass `--github-repo`.
|
|
43
43
|
|
|
44
|
-
The package command recognizes macOS `packagerConfig.osxSign` and `packagerConfig.osxNotarize` options and reports the signing/notarization plan without serializing credential values. When `osxSign` is enabled on macOS and no certificate identity is configured, or the identity is `"-"`, `package` writes an experimental Rust-native ad-hoc signature for the generated `.app` bundle.
|
|
44
|
+
The package command recognizes macOS `packagerConfig.osxSign` and `packagerConfig.osxNotarize` options and reports the signing/notarization plan without serializing credential values. When `osxSign` is enabled on macOS and no certificate identity is configured, or the identity is `"-"`, `package` writes an experimental Rust-native ad-hoc signature for the generated `.app` bundle. When `osxSign.p12File` points at a `.p12`/PFX certificate export, `package` can sign the bundle with that certificate. macOS keychain identity lookup and notarization execution are not implemented yet.
|
|
45
45
|
|
|
46
|
-
The DMG maker is currently a pure-Rust FAT32 image with the app bundle and an Applications entry. The MSI maker writes a compressed embedded CAB, Windows Installer database tables, and a Start Menu shortcut when the packaged executable is present. HFS+/APFS DMG layout customization, installer UI customization, Windows/Linux icon embedding,
|
|
46
|
+
The DMG maker is currently a pure-Rust FAT32 image with the app bundle and an Applications entry. The MSI maker writes a compressed embedded CAB, Windows Installer database tables, and a Start Menu shortcut when the packaged executable is present. HFS+/APFS DMG layout customization, installer UI customization, Windows/Linux icon embedding, macOS keychain signing, timestamping, and notarization execution are still TODO.
|
|
47
47
|
|
|
48
48
|
Package metadata can be configured in `package.json`:
|
|
49
49
|
|
|
@@ -57,7 +57,8 @@ Package metadata can be configured in `package.json`:
|
|
|
57
57
|
"icon": "assets/icon",
|
|
58
58
|
"extraResource": "assets/config.json",
|
|
59
59
|
"osxSign": {
|
|
60
|
-
"
|
|
60
|
+
"p12File": "certs/developer-id.p12",
|
|
61
|
+
"p12PasswordEnv": "ELECTRON_CLI_P12_PASSWORD",
|
|
61
62
|
"entitlements": "assets/entitlements.plist",
|
|
62
63
|
"hardenedRuntime": true
|
|
63
64
|
},
|
|
@@ -97,7 +98,7 @@ Package metadata can be configured in `package.json`:
|
|
|
97
98
|
}
|
|
98
99
|
```
|
|
99
100
|
|
|
100
|
-
Set `identity` to a Developer ID certificate name when you want the plan to reflect Forge-style release signing, but this project will report it as not executable until Rust-native
|
|
101
|
+
Use `p12PasswordEnv`, `p12PasswordFile`, or `p12Password` for the `.p12` password; password values are not serialized in package reports. Set `identity` to a Developer ID certificate name when you want the plan to reflect Forge-style keychain release signing, but this project will report it as not executable until Rust-native keychain lookup exists. Use `identity: "-"` or omit `identity` for the current ad-hoc signing path.
|
|
101
102
|
|
|
102
103
|
The package command also reads JSON-shaped `config.forge.packagerConfig` and `electronPackagerConfig` entries for the same fields. The make command maps JSON-shaped Forge maker names to the Rust-native targets it supports: zip, dmg, deb, rpm, and wix/msi. The publish command maps JSON-shaped publisher names to local and GitHub.
|
|
103
104
|
|
package/package.json
CHANGED
package/src/commands/package.rs
CHANGED
|
@@ -5,7 +5,10 @@ use std::{
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
use anyhow::{bail, Context, Result};
|
|
8
|
-
use apple_codesign::{
|
|
8
|
+
use apple_codesign::{
|
|
9
|
+
cryptography::{parse_pfx_data, PrivateKey},
|
|
10
|
+
BundleSigner, CodeSignatureFlags, SettingsScope, SigningSettings,
|
|
11
|
+
};
|
|
9
12
|
use camino::Utf8PathBuf;
|
|
10
13
|
use plist::{Dictionary as PlistDictionary, Value as PlistValue};
|
|
11
14
|
use serde::Serialize;
|
|
@@ -75,6 +78,12 @@ struct MacosSignPlan {
|
|
|
75
78
|
will_execute: bool,
|
|
76
79
|
method: Option<String>,
|
|
77
80
|
identity: Option<String>,
|
|
81
|
+
p12_file: Option<Utf8PathBuf>,
|
|
82
|
+
p12_password_source: Option<String>,
|
|
83
|
+
p12_password_env: Option<String>,
|
|
84
|
+
p12_password_file: Option<Utf8PathBuf>,
|
|
85
|
+
#[serde(skip)]
|
|
86
|
+
p12_password: RedactedSecret,
|
|
78
87
|
entitlements: Vec<Utf8PathBuf>,
|
|
79
88
|
entitlements_inherit: Option<Utf8PathBuf>,
|
|
80
89
|
hardened_runtime: Option<bool>,
|
|
@@ -90,6 +99,29 @@ struct MacosNotarizePlan {
|
|
|
90
99
|
keychain: Option<String>,
|
|
91
100
|
}
|
|
92
101
|
|
|
102
|
+
#[derive(Clone, Default)]
|
|
103
|
+
struct RedactedSecret(Option<String>);
|
|
104
|
+
|
|
105
|
+
impl std::fmt::Debug for RedactedSecret {
|
|
106
|
+
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
107
|
+
if self.0.is_some() {
|
|
108
|
+
formatter.write_str("<redacted>")
|
|
109
|
+
} else {
|
|
110
|
+
formatter.write_str("<unset>")
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
impl RedactedSecret {
|
|
116
|
+
fn new(value: Option<String>) -> Self {
|
|
117
|
+
Self(value)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
fn as_deref(&self) -> Option<&str> {
|
|
121
|
+
self.0.as_deref()
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
93
125
|
#[derive(Clone, Copy, Debug, Serialize)]
|
|
94
126
|
#[serde(rename_all = "kebab-case")]
|
|
95
127
|
enum PackageStatus {
|
|
@@ -127,6 +159,10 @@ struct MacosSignConfig {
|
|
|
127
159
|
enabled: bool,
|
|
128
160
|
invalid_type: bool,
|
|
129
161
|
identity: Option<String>,
|
|
162
|
+
p12_file: Option<String>,
|
|
163
|
+
p12_password: Option<String>,
|
|
164
|
+
p12_password_env: Option<String>,
|
|
165
|
+
p12_password_file: Option<String>,
|
|
130
166
|
entitlements: Vec<String>,
|
|
131
167
|
entitlements_inherit: Option<String>,
|
|
132
168
|
hardened_runtime: Option<bool>,
|
|
@@ -409,15 +445,36 @@ fn execute_macos_signing(report: &PackageReport) -> Result<()> {
|
|
|
409
445
|
.collect_nested_bundles()
|
|
410
446
|
.context("Could not discover nested macOS bundles for signing")?;
|
|
411
447
|
|
|
412
|
-
let settings = macos_signing_settings(report)?;
|
|
413
|
-
|
|
414
|
-
.
|
|
415
|
-
|
|
416
|
-
format!(
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
)
|
|
420
|
-
|
|
448
|
+
let mut settings = macos_signing_settings(report)?;
|
|
449
|
+
if let Some(p12_file) = &report.signing.macos.sign.p12_file {
|
|
450
|
+
let p12_path = Path::new(p12_file.as_str());
|
|
451
|
+
let p12_data = fs::read(p12_path)
|
|
452
|
+
.with_context(|| format!("Could not read {}", p12_path.display()))?;
|
|
453
|
+
let password = macos_p12_password(&report.signing.macos.sign)?;
|
|
454
|
+
let (certificate, signing_key) = parse_pfx_data(&p12_data, &password)
|
|
455
|
+
.with_context(|| format!("Could not parse {}", p12_path.display()))?;
|
|
456
|
+
|
|
457
|
+
settings.set_signing_key(signing_key.as_key_info_signer(), certificate);
|
|
458
|
+
settings.chain_apple_certificates();
|
|
459
|
+
settings.set_team_id_from_signing_certificate();
|
|
460
|
+
signer
|
|
461
|
+
.write_signed_bundle(&signed_bundle_dir, &settings)
|
|
462
|
+
.with_context(|| {
|
|
463
|
+
format!(
|
|
464
|
+
"Could not write signed macOS bundle to {}",
|
|
465
|
+
signed_bundle_dir.display()
|
|
466
|
+
)
|
|
467
|
+
})?;
|
|
468
|
+
} else {
|
|
469
|
+
signer
|
|
470
|
+
.write_signed_bundle(&signed_bundle_dir, &settings)
|
|
471
|
+
.with_context(|| {
|
|
472
|
+
format!(
|
|
473
|
+
"Could not write signed macOS bundle to {}",
|
|
474
|
+
signed_bundle_dir.display()
|
|
475
|
+
)
|
|
476
|
+
})?;
|
|
477
|
+
}
|
|
421
478
|
|
|
422
479
|
Ok(())
|
|
423
480
|
})();
|
|
@@ -441,7 +498,7 @@ fn execute_macos_signing(report: &PackageReport) -> Result<()> {
|
|
|
441
498
|
Ok(())
|
|
442
499
|
}
|
|
443
500
|
|
|
444
|
-
fn macos_signing_settings(report: &PackageReport) -> Result<SigningSettings<'
|
|
501
|
+
fn macos_signing_settings<'key>(report: &PackageReport) -> Result<SigningSettings<'key>> {
|
|
445
502
|
let sign = &report.signing.macos.sign;
|
|
446
503
|
let mut settings = SigningSettings::default();
|
|
447
504
|
settings.set_binary_identifier(SettingsScope::Main, &report.metadata.bundle_identifier);
|
|
@@ -471,6 +528,37 @@ fn macos_signing_settings(report: &PackageReport) -> Result<SigningSettings<'sta
|
|
|
471
528
|
Ok(settings)
|
|
472
529
|
}
|
|
473
530
|
|
|
531
|
+
fn macos_p12_password(sign: &MacosSignPlan) -> Result<String> {
|
|
532
|
+
if let Some(password) = sign.p12_password.as_deref() {
|
|
533
|
+
return Ok(password.to_string());
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if let Some(env_name) = &sign.p12_password_env {
|
|
537
|
+
return std::env::var(env_name)
|
|
538
|
+
.with_context(|| format!("Could not read macOS signing p12 password env {env_name}"));
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if let Some(path) = &sign.p12_password_file {
|
|
542
|
+
let password_path = Path::new(path.as_str());
|
|
543
|
+
return fs::read_to_string(password_path)
|
|
544
|
+
.with_context(|| {
|
|
545
|
+
format!(
|
|
546
|
+
"Could not read macOS signing p12 password file {}",
|
|
547
|
+
password_path.display()
|
|
548
|
+
)
|
|
549
|
+
})
|
|
550
|
+
.and_then(|contents| {
|
|
551
|
+
contents
|
|
552
|
+
.lines()
|
|
553
|
+
.next()
|
|
554
|
+
.map(str::to_string)
|
|
555
|
+
.context("macOS signing p12 password file is empty")
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
Ok(String::new())
|
|
560
|
+
}
|
|
561
|
+
|
|
474
562
|
fn print_report(report: &PackageReport, json: bool) -> Result<()> {
|
|
475
563
|
if json {
|
|
476
564
|
return output::json(report);
|
|
@@ -507,6 +595,12 @@ fn print_report(report: &PackageReport, json: bool) -> Result<()> {
|
|
|
507
595
|
if let Some(identity) = &report.signing.macos.sign.identity {
|
|
508
596
|
println!(" identity: {identity}");
|
|
509
597
|
}
|
|
598
|
+
if let Some(path) = &report.signing.macos.sign.p12_file {
|
|
599
|
+
println!(" p12 file: {path}");
|
|
600
|
+
}
|
|
601
|
+
if let Some(source) = &report.signing.macos.sign.p12_password_source {
|
|
602
|
+
println!(" p12 password: {source}");
|
|
603
|
+
}
|
|
510
604
|
if let Some(method) = &report.signing.macos.sign.method {
|
|
511
605
|
println!(" signing method: {method}");
|
|
512
606
|
}
|
|
@@ -648,6 +742,26 @@ fn parse_macos_sign_config(value: Option<&JsonValue>) -> MacosSignConfig {
|
|
|
648
742
|
.or_else(|| object.get("identityName"))
|
|
649
743
|
.and_then(JsonValue::as_str)
|
|
650
744
|
.map(ToOwned::to_owned),
|
|
745
|
+
p12_file: object
|
|
746
|
+
.get("p12File")
|
|
747
|
+
.or_else(|| object.get("pfxFile"))
|
|
748
|
+
.and_then(JsonValue::as_str)
|
|
749
|
+
.map(ToOwned::to_owned),
|
|
750
|
+
p12_password: object
|
|
751
|
+
.get("p12Password")
|
|
752
|
+
.or_else(|| object.get("pfxPassword"))
|
|
753
|
+
.and_then(JsonValue::as_str)
|
|
754
|
+
.map(ToOwned::to_owned),
|
|
755
|
+
p12_password_env: object
|
|
756
|
+
.get("p12PasswordEnv")
|
|
757
|
+
.or_else(|| object.get("pfxPasswordEnv"))
|
|
758
|
+
.and_then(JsonValue::as_str)
|
|
759
|
+
.map(ToOwned::to_owned),
|
|
760
|
+
p12_password_file: object
|
|
761
|
+
.get("p12PasswordFile")
|
|
762
|
+
.or_else(|| object.get("pfxPasswordFile"))
|
|
763
|
+
.and_then(JsonValue::as_str)
|
|
764
|
+
.map(ToOwned::to_owned),
|
|
651
765
|
entitlements,
|
|
652
766
|
entitlements_inherit: object
|
|
653
767
|
.get("entitlementsInherit")
|
|
@@ -844,11 +958,60 @@ fn macos_sign_plan(
|
|
|
844
958
|
}
|
|
845
959
|
}
|
|
846
960
|
|
|
961
|
+
let p12_file = config
|
|
962
|
+
.p12_file
|
|
963
|
+
.as_deref()
|
|
964
|
+
.filter(|path| !path.trim().is_empty())
|
|
965
|
+
.map(|path| utf8_path(resolve_project_path(root, path)))
|
|
966
|
+
.transpose()?;
|
|
967
|
+
if let Some(path) = &p12_file {
|
|
968
|
+
if !Path::new(path.as_str()).exists() {
|
|
969
|
+
warnings.push(format!(
|
|
970
|
+
"Configured macOS signing p12 file does not exist: {}.",
|
|
971
|
+
path
|
|
972
|
+
));
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
let p12_password_file = config
|
|
976
|
+
.p12_password_file
|
|
977
|
+
.as_deref()
|
|
978
|
+
.filter(|path| !path.trim().is_empty())
|
|
979
|
+
.map(|path| utf8_path(resolve_project_path(root, path)))
|
|
980
|
+
.transpose()?;
|
|
981
|
+
if let Some(path) = &p12_password_file {
|
|
982
|
+
if !Path::new(path.as_str()).exists() {
|
|
983
|
+
warnings.push(format!(
|
|
984
|
+
"Configured macOS signing p12 password file does not exist: {}.",
|
|
985
|
+
path
|
|
986
|
+
));
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
let p12_password_source = if p12_file.is_some() {
|
|
990
|
+
if config.p12_password.is_some() {
|
|
991
|
+
Some("config".to_string())
|
|
992
|
+
} else if let Some(env_name) = config
|
|
993
|
+
.p12_password_env
|
|
994
|
+
.as_deref()
|
|
995
|
+
.filter(|name| !name.trim().is_empty())
|
|
996
|
+
{
|
|
997
|
+
Some(format!("env:{env_name}"))
|
|
998
|
+
} else if let Some(path) = &p12_password_file {
|
|
999
|
+
Some(format!("file:{path}"))
|
|
1000
|
+
} else {
|
|
1001
|
+
Some("empty".to_string())
|
|
1002
|
+
}
|
|
1003
|
+
} else {
|
|
1004
|
+
None
|
|
1005
|
+
};
|
|
1006
|
+
|
|
847
1007
|
let identity = config.identity.as_deref().map(str::trim);
|
|
848
1008
|
let ad_hoc_identity = matches!(identity, None | Some("-"));
|
|
849
|
-
let
|
|
1009
|
+
let p12_identity = p12_file.is_some();
|
|
1010
|
+
let will_execute = config.enabled && platform == "darwin" && (ad_hoc_identity || p12_identity);
|
|
850
1011
|
let method = if config.enabled && platform == "darwin" {
|
|
851
|
-
if
|
|
1012
|
+
if p12_identity {
|
|
1013
|
+
Some("certificate-p12".to_string())
|
|
1014
|
+
} else if ad_hoc_identity {
|
|
852
1015
|
Some("ad-hoc".to_string())
|
|
853
1016
|
} else {
|
|
854
1017
|
Some("certificate-identity".to_string())
|
|
@@ -863,17 +1026,22 @@ fn macos_sign_plan(
|
|
|
863
1026
|
));
|
|
864
1027
|
} else if config.enabled && !will_execute {
|
|
865
1028
|
warnings.push(
|
|
866
|
-
"macOS signing identity is configured, but Rust-native
|
|
1029
|
+
"macOS signing identity is configured, but Rust-native keychain identity signing is not implemented yet; package output will be unsigned. Use p12File for certificate signing, or identity '-' / omit identity for experimental ad-hoc signing.".to_string(),
|
|
867
1030
|
);
|
|
868
1031
|
} else if will_execute {
|
|
1032
|
+
if p12_identity && identity.is_some() {
|
|
1033
|
+
warnings.push(
|
|
1034
|
+
"packagerConfig.osxSign.p12File supplies the signing certificate; identity is reported but not used for keychain lookup.".to_string(),
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
869
1037
|
if config.entitlements.len() > 1 {
|
|
870
1038
|
warnings.push(
|
|
871
|
-
"Rust-native
|
|
1039
|
+
"Rust-native macOS signing applies the first macOS entitlements file only; inherited/login-helper entitlement scoping is not implemented yet.".to_string(),
|
|
872
1040
|
);
|
|
873
1041
|
}
|
|
874
1042
|
if config.entitlements_inherit.is_some() {
|
|
875
1043
|
warnings.push(
|
|
876
|
-
"packagerConfig.osxSign.entitlementsInherit is recognized but not applied to nested bundles by Rust-native
|
|
1044
|
+
"packagerConfig.osxSign.entitlementsInherit is recognized but not applied to nested bundles by Rust-native signing yet.".to_string(),
|
|
877
1045
|
);
|
|
878
1046
|
}
|
|
879
1047
|
if config.gatekeeper_assess.is_some() {
|
|
@@ -889,6 +1057,11 @@ fn macos_sign_plan(
|
|
|
889
1057
|
will_execute,
|
|
890
1058
|
method,
|
|
891
1059
|
identity: config.identity.clone(),
|
|
1060
|
+
p12_file,
|
|
1061
|
+
p12_password_source,
|
|
1062
|
+
p12_password_env: config.p12_password_env.clone(),
|
|
1063
|
+
p12_password_file,
|
|
1064
|
+
p12_password: RedactedSecret::new(config.p12_password.clone()),
|
|
892
1065
|
entitlements,
|
|
893
1066
|
entitlements_inherit,
|
|
894
1067
|
hardened_runtime: config.hardened_runtime,
|
|
@@ -926,6 +1099,7 @@ fn macos_notarize_plan(
|
|
|
926
1099
|
if config.enabled
|
|
927
1100
|
&& platform == "darwin"
|
|
928
1101
|
&& package_config.packager.osx_sign.enabled
|
|
1102
|
+
&& package_config.packager.osx_sign.p12_file.is_none()
|
|
929
1103
|
&& matches!(
|
|
930
1104
|
package_config
|
|
931
1105
|
.packager
|
|
@@ -2008,7 +2182,7 @@ mod tests {
|
|
|
2008
2182
|
assert!(report
|
|
2009
2183
|
.warnings
|
|
2010
2184
|
.iter()
|
|
2011
|
-
.any(|warning| warning.contains("Rust-native
|
|
2185
|
+
.any(|warning| warning.contains("Rust-native keychain identity signing")));
|
|
2012
2186
|
assert!(report
|
|
2013
2187
|
.warnings
|
|
2014
2188
|
.iter()
|
|
@@ -2063,13 +2237,74 @@ mod tests {
|
|
|
2063
2237
|
assert_eq!(report.signing.macos.sign.identity.as_deref(), Some("-"));
|
|
2064
2238
|
assert_eq!(report.signing.macos.sign.hardened_runtime, Some(true));
|
|
2065
2239
|
assert!(!report.warnings.iter().any(|warning| {
|
|
2066
|
-
warning.contains("Rust-native
|
|
2240
|
+
warning.contains("Rust-native keychain identity signing")
|
|
2067
2241
|
|| warning.contains("Rust-native signing is not implemented")
|
|
2068
2242
|
}));
|
|
2069
2243
|
|
|
2070
2244
|
let _ = fs::remove_dir_all(root);
|
|
2071
2245
|
}
|
|
2072
2246
|
|
|
2247
|
+
#[test]
|
|
2248
|
+
fn plans_macos_p12_signing_without_serializing_password() {
|
|
2249
|
+
let root = unique_temp_dir("macos-p12-signing-plan");
|
|
2250
|
+
write_package_json(&root);
|
|
2251
|
+
fs::write(root.join("developer-id.p12"), "not a real p12")
|
|
2252
|
+
.expect("p12 placeholder should be written");
|
|
2253
|
+
fs::write(
|
|
2254
|
+
root.join("forge.config.js"),
|
|
2255
|
+
r#"
|
|
2256
|
+
module.exports = {
|
|
2257
|
+
packagerConfig: {
|
|
2258
|
+
osxSign: {
|
|
2259
|
+
identity: 'Developer ID Application: Example, Inc. (TEAMID1234)',
|
|
2260
|
+
p12File: 'developer-id.p12',
|
|
2261
|
+
p12Password: 'p12-secret',
|
|
2262
|
+
hardenedRuntime: true,
|
|
2263
|
+
},
|
|
2264
|
+
},
|
|
2265
|
+
};
|
|
2266
|
+
"#,
|
|
2267
|
+
)
|
|
2268
|
+
.expect("forge config should be written");
|
|
2269
|
+
write_app_file(&root);
|
|
2270
|
+
write_fake_electron_dist(&root);
|
|
2271
|
+
|
|
2272
|
+
let args = PackageArgs {
|
|
2273
|
+
cwd: root.clone(),
|
|
2274
|
+
out_dir: PathBuf::from("out"),
|
|
2275
|
+
name: None,
|
|
2276
|
+
platform: Some("darwin".to_string()),
|
|
2277
|
+
arch: Some("arm64".to_string()),
|
|
2278
|
+
force: false,
|
|
2279
|
+
dry_run: true,
|
|
2280
|
+
json: true,
|
|
2281
|
+
};
|
|
2282
|
+
let snapshot = crate::project::inspect(&root).expect("project should inspect");
|
|
2283
|
+
let report = build_report(snapshot, &args).expect("report should build");
|
|
2284
|
+
|
|
2285
|
+
assert!(report.signing.macos.sign.configured);
|
|
2286
|
+
assert!(report.signing.macos.sign.enabled);
|
|
2287
|
+
assert!(report.signing.macos.sign.will_execute);
|
|
2288
|
+
assert_eq!(
|
|
2289
|
+
report.signing.macos.sign.method.as_deref(),
|
|
2290
|
+
Some("certificate-p12")
|
|
2291
|
+
);
|
|
2292
|
+
assert_eq!(
|
|
2293
|
+
report.signing.macos.sign.p12_password_source.as_deref(),
|
|
2294
|
+
Some("config")
|
|
2295
|
+
);
|
|
2296
|
+
assert!(report.signing.macos.sign.p12_file.is_some());
|
|
2297
|
+
assert!(report
|
|
2298
|
+
.warnings
|
|
2299
|
+
.iter()
|
|
2300
|
+
.any(|warning| { warning.contains("p12File supplies the signing certificate") }));
|
|
2301
|
+
|
|
2302
|
+
let json = serde_json::to_string(&report).expect("report should serialize");
|
|
2303
|
+
assert!(!json.contains("p12-secret"));
|
|
2304
|
+
|
|
2305
|
+
let _ = fs::remove_dir_all(root);
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2073
2308
|
#[test]
|
|
2074
2309
|
fn warns_when_macos_notarization_is_configured_without_signing() {
|
|
2075
2310
|
let root = unique_temp_dir("notarize-without-sign");
|
|
@@ -2252,6 +2487,69 @@ mod tests {
|
|
|
2252
2487
|
let _ = fs::remove_dir_all(root);
|
|
2253
2488
|
}
|
|
2254
2489
|
|
|
2490
|
+
#[test]
|
|
2491
|
+
fn packages_macos_bundle_with_p12_certificate_signature() {
|
|
2492
|
+
if current_platform() != "darwin" {
|
|
2493
|
+
return;
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
let Some(p12_fixture) = apple_codesign_test_fixture("apple-codesign-testuser.p12") else {
|
|
2497
|
+
return;
|
|
2498
|
+
};
|
|
2499
|
+
|
|
2500
|
+
let root = unique_temp_dir("macos-p12-signing-execute");
|
|
2501
|
+
fs::copy(&p12_fixture, root.join("developer-id.p12"))
|
|
2502
|
+
.expect("p12 fixture should be copied");
|
|
2503
|
+
fs::write(
|
|
2504
|
+
root.join("package.json"),
|
|
2505
|
+
r#"{"name":"starter-app","version":"0.1.0","main":"src/main.js","devDependencies":{"electron":"30.0.0"},"electronCli":{"packagerConfig":{"appBundleId":"com.example.p12-signed","osxSign":{"p12File":"developer-id.p12","p12Password":"password123","hardenedRuntime":true}}}}"#,
|
|
2506
|
+
)
|
|
2507
|
+
.expect("package.json should be written");
|
|
2508
|
+
write_app_file(&root);
|
|
2509
|
+
write_macho_electron_dist(&root);
|
|
2510
|
+
|
|
2511
|
+
let args = PackageArgs {
|
|
2512
|
+
cwd: root.clone(),
|
|
2513
|
+
out_dir: PathBuf::from("out"),
|
|
2514
|
+
name: None,
|
|
2515
|
+
platform: None,
|
|
2516
|
+
arch: None,
|
|
2517
|
+
force: false,
|
|
2518
|
+
dry_run: false,
|
|
2519
|
+
json: false,
|
|
2520
|
+
};
|
|
2521
|
+
let snapshot = crate::project::inspect(&root).expect("project should inspect");
|
|
2522
|
+
let report = build_report(snapshot, &args).expect("report should build");
|
|
2523
|
+
|
|
2524
|
+
assert!(report.signing.macos.sign.will_execute);
|
|
2525
|
+
assert_eq!(
|
|
2526
|
+
report.signing.macos.sign.method.as_deref(),
|
|
2527
|
+
Some("certificate-p12")
|
|
2528
|
+
);
|
|
2529
|
+
assert!(report.warnings.is_empty());
|
|
2530
|
+
|
|
2531
|
+
execute_package(&report, false).expect("package should succeed");
|
|
2532
|
+
|
|
2533
|
+
let executable = Path::new(report.bundle_dir.as_str())
|
|
2534
|
+
.join("Contents/MacOS")
|
|
2535
|
+
.join(&report.executable_name);
|
|
2536
|
+
let executable_data = fs::read(executable).expect("signed executable should read");
|
|
2537
|
+
let macho = apple_codesign::MachFile::parse(&executable_data)
|
|
2538
|
+
.expect("signed executable should parse as Mach-O");
|
|
2539
|
+
assert!(macho.iter_macho().all(|binary| {
|
|
2540
|
+
let signature = binary
|
|
2541
|
+
.code_signature()
|
|
2542
|
+
.expect("code signature should parse")
|
|
2543
|
+
.expect("code signature should exist");
|
|
2544
|
+
signature
|
|
2545
|
+
.signature_data()
|
|
2546
|
+
.expect("CMS signature should parse")
|
|
2547
|
+
.is_some_and(|data| !data.is_empty())
|
|
2548
|
+
}));
|
|
2549
|
+
|
|
2550
|
+
let _ = fs::remove_dir_all(root);
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2255
2553
|
#[test]
|
|
2256
2554
|
fn missing_required_runtime_dependency_fails() {
|
|
2257
2555
|
let root = unique_temp_dir("runtime-deps");
|
|
@@ -2415,6 +2713,31 @@ mod tests {
|
|
|
2415
2713
|
.expect("Mach-O test executable should be copied");
|
|
2416
2714
|
}
|
|
2417
2715
|
|
|
2716
|
+
fn apple_codesign_test_fixture(file_name: &str) -> Option<PathBuf> {
|
|
2717
|
+
let cargo_home = std::env::var_os("CARGO_HOME")
|
|
2718
|
+
.map(PathBuf::from)
|
|
2719
|
+
.or_else(|| std::env::var_os("HOME").map(|home| PathBuf::from(home).join(".cargo")))?;
|
|
2720
|
+
let registry_src = cargo_home.join("registry/src");
|
|
2721
|
+
for index_dir in fs::read_dir(registry_src).ok()? {
|
|
2722
|
+
let index_dir = index_dir.ok()?;
|
|
2723
|
+
for crate_dir in fs::read_dir(index_dir.path()).ok()? {
|
|
2724
|
+
let crate_dir = crate_dir.ok()?;
|
|
2725
|
+
let file_name_matches = crate_dir
|
|
2726
|
+
.file_name()
|
|
2727
|
+
.to_str()
|
|
2728
|
+
.is_some_and(|name| name.starts_with("apple-codesign-"));
|
|
2729
|
+
if file_name_matches {
|
|
2730
|
+
let candidate = crate_dir.path().join("src").join(file_name);
|
|
2731
|
+
if candidate.exists() {
|
|
2732
|
+
return Some(candidate);
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2738
|
+
None
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2418
2741
|
fn unique_temp_dir(label: &str) -> PathBuf {
|
|
2419
2742
|
let nanos = std::time::SystemTime::now()
|
|
2420
2743
|
.duration_since(std::time::UNIX_EPOCH)
|