1use {
8 crate::{
9 certificate::{AppleCertificate, CodeSigningCertificateExtension},
10 code_directory::CodeSignatureFlags,
11 code_requirement::CodeRequirementExpression,
12 cryptography::DigestType,
13 embedded_signature::{Blob, RequirementBlob},
14 environment_constraints::EncodedEnvironmentConstraints,
15 error::AppleCodesignError,
16 macho::{parse_version_nibbles, MachFile},
17 },
18 glob::Pattern,
19 goblin::mach::cputype::{
20 CpuType, CPU_TYPE_ARM, CPU_TYPE_ARM64, CPU_TYPE_ARM64_32, CPU_TYPE_X86_64,
21 },
22 log::{error, info},
23 reqwest::{IntoUrl, Url},
24 std::{
25 collections::{BTreeMap, BTreeSet},
26 fmt::Formatter,
27 },
28 x509_certificate::{CapturedX509Certificate, KeyInfoSigner},
29};
30
31#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
60pub enum SettingsScope {
61 Main,
72
73 Path(String),
81
82 MultiArchIndex(usize),
87
88 MultiArchCpuType(CpuType),
92
93 PathMultiArchIndex(String, usize),
98
99 PathMultiArchCpuType(String, CpuType),
104}
105
106impl std::fmt::Display for SettingsScope {
107 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
108 match self {
109 Self::Main => f.write_str("main signing target"),
110 Self::Path(path) => f.write_fmt(format_args!("path {path}")),
111 Self::MultiArchIndex(index) => f.write_fmt(format_args!(
112 "fat/universal Mach-O binaries at index {index}"
113 )),
114 Self::MultiArchCpuType(cpu_type) => f.write_fmt(format_args!(
115 "fat/universal Mach-O binaries for CPU {cpu_type}"
116 )),
117 Self::PathMultiArchIndex(path, index) => f.write_fmt(format_args!(
118 "fat/universal Mach-O binaries at index {index} under path {path}"
119 )),
120 Self::PathMultiArchCpuType(path, cpu_type) => f.write_fmt(format_args!(
121 "fat/universal Mach-O binaries for CPU {cpu_type} under path {path}"
122 )),
123 }
124 }
125}
126
127impl SettingsScope {
128 fn parse_at_expr(
129 at_expr: &str,
130 ) -> Result<(Option<usize>, Option<CpuType>), AppleCodesignError> {
131 match at_expr.parse::<usize>() {
132 Ok(index) => Ok((Some(index), None)),
133 Err(_) => {
134 if at_expr.starts_with('[') && at_expr.ends_with(']') {
135 let v = &at_expr[1..at_expr.len() - 1];
136 let parts = v.split('=').collect::<Vec<_>>();
137
138 if parts.len() == 2 {
139 let (key, value) = (parts[0], parts[1]);
140
141 if key != "cpu_type" {
142 return Err(AppleCodesignError::ParseSettingsScope(format!(
143 "in '@{at_expr}', {key} not recognized; must be cpu_type"
144 )));
145 }
146
147 if let Some(cpu_type) = match value {
148 "arm" => Some(CPU_TYPE_ARM),
149 "arm64" => Some(CPU_TYPE_ARM64),
150 "arm64_32" => Some(CPU_TYPE_ARM64_32),
151 "x86_64" => Some(CPU_TYPE_X86_64),
152 _ => None,
153 } {
154 return Ok((None, Some(cpu_type)));
155 }
156
157 match value.parse::<u32>() {
158 Ok(cpu_type) => Ok((None, Some(cpu_type as CpuType))),
159 Err(_) => Err(AppleCodesignError::ParseSettingsScope(format!(
160 "in '@{at_expr}', cpu_arch value {value} not recognized"
161 ))),
162 }
163 } else {
164 Err(AppleCodesignError::ParseSettingsScope(format!(
165 "'{v}' sub-expression isn't of form <key>=<value>"
166 )))
167 }
168 } else {
169 Err(AppleCodesignError::ParseSettingsScope(format!(
170 "in '{at_expr}', @ expression not recognized"
171 )))
172 }
173 }
174 }
175 }
176}
177
178impl AsRef<SettingsScope> for SettingsScope {
179 fn as_ref(&self) -> &SettingsScope {
180 self
181 }
182}
183
184impl TryFrom<&str> for SettingsScope {
185 type Error = AppleCodesignError;
186
187 fn try_from(s: &str) -> Result<Self, Self::Error> {
188 if s == "@main" {
189 Ok(Self::Main)
190 } else if let Some(at_expr) = s.strip_prefix('@') {
191 match Self::parse_at_expr(at_expr)? {
192 (Some(index), None) => Ok(Self::MultiArchIndex(index)),
193 (None, Some(cpu_type)) => Ok(Self::MultiArchCpuType(cpu_type)),
194 _ => panic!("this shouldn't happen"),
195 }
196 } else {
197 let parts = s.rsplitn(2, '@').collect::<Vec<_>>();
199
200 match parts.len() {
201 1 => Ok(Self::Path(s.to_string())),
202 2 => {
203 let (at_expr, path) = (parts[0], parts[1]);
205
206 match Self::parse_at_expr(at_expr)? {
207 (Some(index), None) => {
208 Ok(Self::PathMultiArchIndex(path.to_string(), index))
209 }
210 (None, Some(cpu_type)) => {
211 Ok(Self::PathMultiArchCpuType(path.to_string(), cpu_type))
212 }
213 _ => panic!("this shouldn't happen"),
214 }
215 }
216 _ => panic!("this shouldn't happen"),
217 }
218 }
219 }
220}
221
222#[derive(Clone, Debug)]
224pub enum DesignatedRequirementMode {
225 Auto,
228
229 Explicit(Vec<Vec<u8>>),
231}
232
233#[derive(Clone, Copy, Debug, Eq, PartialEq)]
235pub enum ScopedSetting {
236 Digest,
237 BinaryIdentifier,
238 Entitlements,
239 DesignatedRequirements,
240 CodeSignatureFlags,
241 RuntimeVersion,
242 InfoPlist,
243 CodeResources,
244 ExtraDigests,
245 LaunchConstraintsSelf,
246 LaunchConstraintsParent,
247 LaunchConstraintsResponsible,
248 LibraryConstraints,
249}
250
251impl ScopedSetting {
252 pub fn all() -> &'static [Self] {
253 &[
254 Self::Digest,
255 Self::BinaryIdentifier,
256 Self::Entitlements,
257 Self::DesignatedRequirements,
258 Self::CodeSignatureFlags,
259 Self::RuntimeVersion,
260 Self::InfoPlist,
261 Self::CodeResources,
262 Self::ExtraDigests,
263 Self::LaunchConstraintsSelf,
264 Self::LaunchConstraintsParent,
265 Self::LaunchConstraintsResponsible,
266 Self::LibraryConstraints,
267 ]
268 }
269
270 pub fn inherit_nested_bundle() -> &'static [Self] {
271 &[Self::Digest, Self::ExtraDigests, Self::RuntimeVersion]
272 }
273
274 pub fn inherit_nested_macho() -> &'static [Self] {
275 &[Self::Digest, Self::ExtraDigests, Self::RuntimeVersion]
276 }
277}
278
279#[derive(Clone, Default)]
298pub struct SigningSettings<'key> {
299 signing_key: Option<(&'key dyn KeyInfoSigner, CapturedX509Certificate)>,
301 certificates: Vec<CapturedX509Certificate>,
302 time_stamp_url: Option<Url>,
303 signing_time: Option<chrono::DateTime<chrono::Utc>>,
304 path_exclusion_patterns: Vec<Pattern>,
305 shallow: bool,
306 for_notarization: bool,
307
308 digest_type: BTreeMap<SettingsScope, DigestType>,
312 team_id: BTreeMap<SettingsScope, String>,
313 identifiers: BTreeMap<SettingsScope, String>,
314 entitlements: BTreeMap<SettingsScope, plist::Value>,
315 designated_requirement: BTreeMap<SettingsScope, DesignatedRequirementMode>,
316 code_signature_flags: BTreeMap<SettingsScope, CodeSignatureFlags>,
317 runtime_version: BTreeMap<SettingsScope, semver::Version>,
318 info_plist_data: BTreeMap<SettingsScope, Vec<u8>>,
319 code_resources_data: BTreeMap<SettingsScope, Vec<u8>>,
320 extra_digests: BTreeMap<SettingsScope, BTreeSet<DigestType>>,
321 launch_constraints_self: BTreeMap<SettingsScope, EncodedEnvironmentConstraints>,
322 launch_constraints_parent: BTreeMap<SettingsScope, EncodedEnvironmentConstraints>,
323 launch_constraints_responsible: BTreeMap<SettingsScope, EncodedEnvironmentConstraints>,
324 library_constraints: BTreeMap<SettingsScope, EncodedEnvironmentConstraints>,
325}
326
327impl<'key> SigningSettings<'key> {
328 pub fn signing_key(&self) -> Option<(&'key dyn KeyInfoSigner, &CapturedX509Certificate)> {
330 self.signing_key.as_ref().map(|(key, cert)| (*key, cert))
331 }
332
333 pub fn set_signing_key(
340 &mut self,
341 private: &'key dyn KeyInfoSigner,
342 public: CapturedX509Certificate,
343 ) {
344 self.signing_key = Some((private, public));
345 }
346
347 pub fn certificate_chain(&self) -> &[CapturedX509Certificate] {
349 &self.certificates
350 }
351
352 pub fn chain_apple_certificates(&mut self) -> Option<Vec<CapturedX509Certificate>> {
358 if let Some((_, cert)) = &self.signing_key {
359 if let Some(chain) = cert.apple_root_certificate_chain() {
360 let chain = chain.into_iter().skip(1).collect::<Vec<_>>();
362 self.certificates.extend(chain.clone());
363 Some(chain)
364 } else {
365 None
366 }
367 } else {
368 None
369 }
370 }
371
372 pub fn signing_certificate_apple_signed(&self) -> bool {
374 if let Some((_, cert)) = &self.signing_key {
375 cert.chains_to_apple_root_ca()
376 } else {
377 false
378 }
379 }
380
381 pub fn chain_certificate(&mut self, cert: CapturedX509Certificate) {
392 self.certificates.push(cert);
393 }
394
395 pub fn chain_certificate_der(
400 &mut self,
401 data: impl AsRef<[u8]>,
402 ) -> Result<(), AppleCodesignError> {
403 self.chain_certificate(CapturedX509Certificate::from_der(data.as_ref())?);
404
405 Ok(())
406 }
407
408 pub fn chain_certificate_pem(
417 &mut self,
418 data: impl AsRef<[u8]>,
419 ) -> Result<(), AppleCodesignError> {
420 self.chain_certificate(CapturedX509Certificate::from_pem(data.as_ref())?);
421
422 Ok(())
423 }
424
425 pub fn time_stamp_url(&self) -> Option<&Url> {
427 self.time_stamp_url.as_ref()
428 }
429
430 pub fn set_time_stamp_url(&mut self, url: impl IntoUrl) -> Result<(), AppleCodesignError> {
439 self.time_stamp_url = Some(url.into_url()?);
440
441 Ok(())
442 }
443
444 pub fn signing_time(&self) -> Option<chrono::DateTime<chrono::Utc>> {
448 self.signing_time
449 }
450
451 pub fn set_signing_time(&mut self, time: chrono::DateTime<chrono::Utc>) {
455 self.signing_time = Some(time);
456 }
457
458 pub fn team_id(&self) -> Option<&str> {
460 self.team_id.get(&SettingsScope::Main).map(|x| x.as_str())
461 }
462
463 pub fn set_team_id(&mut self, value: impl ToString) {
465 self.team_id.insert(SettingsScope::Main, value.to_string());
466 }
467
468 pub fn set_team_id_from_signing_certificate(&mut self) -> Option<&str> {
485 if !self.signing_certificate_apple_signed() {
487 None
488 } else if let Some((_, cert)) = &self.signing_key {
489 if let Some(team_id) = cert.apple_team_id() {
490 self.set_team_id(team_id);
491 Some(
492 self.team_id
493 .get(&SettingsScope::Main)
494 .expect("we just set a team id"),
495 )
496 } else {
497 None
498 }
499 } else {
500 None
501 }
502 }
503
504 pub fn path_exclusion_pattern_matches(&self, path: &str) -> bool {
506 self.path_exclusion_patterns
507 .iter()
508 .any(|pattern| pattern.matches(path))
509 }
510
511 pub fn add_path_exclusion(&mut self, v: &str) -> Result<(), AppleCodesignError> {
513 self.path_exclusion_patterns.push(Pattern::new(v)?);
514 Ok(())
515 }
516
517 pub fn shallow(&self) -> bool {
522 self.shallow
523 }
524
525 pub fn set_shallow(&mut self, v: bool) {
527 self.shallow = v;
528 }
529
530 pub fn for_notarization(&self) -> bool {
535 self.for_notarization
536 }
537
538 pub fn set_for_notarization(&mut self, v: bool) {
540 self.for_notarization = v;
541 }
542
543 pub fn digest_type(&self, scope: impl AsRef<SettingsScope>) -> DigestType {
545 self.digest_type
546 .get(scope.as_ref())
547 .copied()
548 .unwrap_or_default()
549 }
550
551 pub fn set_digest_type(&mut self, scope: SettingsScope, digest_type: DigestType) {
557 self.digest_type.insert(scope, digest_type);
558 }
559
560 pub fn binary_identifier(&self, scope: impl AsRef<SettingsScope>) -> Option<&str> {
562 self.identifiers.get(scope.as_ref()).map(|s| s.as_str())
563 }
564
565 pub fn set_binary_identifier(&mut self, scope: SettingsScope, value: impl ToString) {
575 self.identifiers.insert(scope, value.to_string());
576 }
577
578 pub fn entitlements_plist(&self, scope: impl AsRef<SettingsScope>) -> Option<&plist::Value> {
582 self.entitlements.get(scope.as_ref())
583 }
584
585 pub fn entitlements_xml(
587 &self,
588 scope: impl AsRef<SettingsScope>,
589 ) -> Result<Option<String>, AppleCodesignError> {
590 if let Some(value) = self.entitlements_plist(scope) {
591 let mut buffer = vec![];
592 let writer = std::io::Cursor::new(&mut buffer);
593 value
594 .to_writer_xml(writer)
595 .map_err(AppleCodesignError::PlistSerializeXml)?;
596
597 Ok(Some(
598 String::from_utf8(buffer).expect("plist XML serialization should produce UTF-8"),
599 ))
600 } else {
601 Ok(None)
602 }
603 }
604
605 pub fn set_entitlements_xml(
610 &mut self,
611 scope: SettingsScope,
612 value: impl ToString,
613 ) -> Result<(), AppleCodesignError> {
614 let cursor = std::io::Cursor::new(value.to_string().into_bytes());
615 let value =
616 plist::Value::from_reader_xml(cursor).map_err(AppleCodesignError::PlistParseXml)?;
617
618 self.entitlements.insert(scope, value);
619
620 Ok(())
621 }
622
623 pub fn designated_requirement(
625 &self,
626 scope: impl AsRef<SettingsScope>,
627 ) -> &DesignatedRequirementMode {
628 self.designated_requirement
629 .get(scope.as_ref())
630 .unwrap_or(&DesignatedRequirementMode::Auto)
631 }
632
633 pub fn set_designated_requirement_expression(
640 &mut self,
641 scope: SettingsScope,
642 expr: &CodeRequirementExpression,
643 ) -> Result<(), AppleCodesignError> {
644 self.designated_requirement.insert(
645 scope,
646 DesignatedRequirementMode::Explicit(vec![expr.to_bytes()?]),
647 );
648
649 Ok(())
650 }
651
652 pub fn set_designated_requirement_bytes(
659 &mut self,
660 scope: SettingsScope,
661 data: impl AsRef<[u8]>,
662 ) -> Result<(), AppleCodesignError> {
663 let blob = RequirementBlob::from_blob_bytes(data.as_ref())?;
664
665 self.designated_requirement.insert(
666 scope,
667 DesignatedRequirementMode::Explicit(
668 blob.parse_expressions()?
669 .iter()
670 .map(|x| x.to_bytes())
671 .collect::<Result<Vec<_>, AppleCodesignError>>()?,
672 ),
673 );
674
675 Ok(())
676 }
677
678 pub fn set_auto_designated_requirement(&mut self, scope: SettingsScope) {
688 self.designated_requirement
689 .insert(scope, DesignatedRequirementMode::Auto);
690 }
691
692 pub fn code_signature_flags(
694 &self,
695 scope: impl AsRef<SettingsScope>,
696 ) -> Option<CodeSignatureFlags> {
697 let mut flags = self.code_signature_flags.get(scope.as_ref()).copied();
698
699 if self.for_notarization {
700 flags.get_or_insert(CodeSignatureFlags::default());
701
702 flags.as_mut().map(|flags| {
703 if !flags.contains(CodeSignatureFlags::RUNTIME) {
704 info!("adding hardened runtime flag because notarization mode enabled");
705 }
706
707 flags.insert(CodeSignatureFlags::RUNTIME);
708 });
709 }
710
711 flags
712 }
713
714 pub fn set_code_signature_flags(&mut self, scope: SettingsScope, flags: CodeSignatureFlags) {
718 self.code_signature_flags.insert(scope, flags);
719 }
720
721 pub fn add_code_signature_flags(
726 &mut self,
727 scope: SettingsScope,
728 flags: CodeSignatureFlags,
729 ) -> CodeSignatureFlags {
730 let existing = self
731 .code_signature_flags
732 .get(&scope)
733 .copied()
734 .unwrap_or_else(CodeSignatureFlags::empty);
735
736 let new = existing | flags;
737
738 self.code_signature_flags.insert(scope, new);
739
740 new
741 }
742
743 pub fn remove_code_signature_flags(
748 &mut self,
749 scope: SettingsScope,
750 flags: CodeSignatureFlags,
751 ) -> CodeSignatureFlags {
752 let existing = self
753 .code_signature_flags
754 .get(&scope)
755 .copied()
756 .unwrap_or_else(CodeSignatureFlags::empty);
757
758 let new = existing - flags;
759
760 self.code_signature_flags.insert(scope, new);
761
762 new
763 }
764
765 pub fn info_plist_data(&self, scope: impl AsRef<SettingsScope>) -> Option<&[u8]> {
767 self.info_plist_data
768 .get(scope.as_ref())
769 .map(|x| x.as_slice())
770 }
771
772 pub fn runtime_version(&self, scope: impl AsRef<SettingsScope>) -> Option<&semver::Version> {
776 self.runtime_version.get(scope.as_ref())
777 }
778
779 pub fn set_runtime_version(&mut self, scope: SettingsScope, version: semver::Version) {
784 self.runtime_version.insert(scope, version);
785 }
786
787 pub fn set_info_plist_data(&mut self, scope: SettingsScope, data: Vec<u8>) {
802 self.info_plist_data.insert(scope, data);
803 }
804
805 pub fn code_resources_data(&self, scope: impl AsRef<SettingsScope>) -> Option<&[u8]> {
807 self.code_resources_data
808 .get(scope.as_ref())
809 .map(|x| x.as_slice())
810 }
811
812 pub fn set_code_resources_data(&mut self, scope: SettingsScope, data: Vec<u8>) {
828 self.code_resources_data.insert(scope, data);
829 }
830
831 pub fn extra_digests(&self, scope: impl AsRef<SettingsScope>) -> Option<&BTreeSet<DigestType>> {
833 self.extra_digests.get(scope.as_ref())
834 }
835
836 pub fn add_extra_digest(&mut self, scope: SettingsScope, digest_type: DigestType) {
846 self.extra_digests
847 .entry(scope)
848 .or_default()
849 .insert(digest_type);
850 }
851
852 pub fn all_digests(&self, scope: SettingsScope) -> Vec<DigestType> {
854 let mut res = vec![self.digest_type(scope.clone())];
855
856 if let Some(extra) = self.extra_digests(scope) {
857 res.extend(extra.iter());
858 }
859
860 res
861 }
862
863 pub fn launch_constraints_self(
865 &self,
866 scope: impl AsRef<SettingsScope>,
867 ) -> Option<&EncodedEnvironmentConstraints> {
868 self.launch_constraints_self.get(scope.as_ref())
869 }
870
871 pub fn set_launch_constraints_self(
873 &mut self,
874 scope: SettingsScope,
875 constraints: EncodedEnvironmentConstraints,
876 ) {
877 self.launch_constraints_self.insert(scope, constraints);
878 }
879
880 pub fn launch_constraints_parent(
882 &self,
883 scope: impl AsRef<SettingsScope>,
884 ) -> Option<&EncodedEnvironmentConstraints> {
885 self.launch_constraints_parent.get(scope.as_ref())
886 }
887
888 pub fn set_launch_constraints_parent(
890 &mut self,
891 scope: SettingsScope,
892 constraints: EncodedEnvironmentConstraints,
893 ) {
894 self.launch_constraints_parent.insert(scope, constraints);
895 }
896
897 pub fn launch_constraints_responsible(
899 &self,
900 scope: impl AsRef<SettingsScope>,
901 ) -> Option<&EncodedEnvironmentConstraints> {
902 self.launch_constraints_responsible.get(scope.as_ref())
903 }
904
905 pub fn set_launch_constraints_responsible(
907 &mut self,
908 scope: SettingsScope,
909 constraints: EncodedEnvironmentConstraints,
910 ) {
911 self.launch_constraints_responsible
912 .insert(scope, constraints);
913 }
914
915 pub fn library_constraints(
917 &self,
918 scope: impl AsRef<SettingsScope>,
919 ) -> Option<&EncodedEnvironmentConstraints> {
920 self.library_constraints.get(scope.as_ref())
921 }
922
923 pub fn set_library_constraints(
925 &mut self,
926 scope: SettingsScope,
927 constraints: EncodedEnvironmentConstraints,
928 ) {
929 self.library_constraints.insert(scope, constraints);
930 }
931
932 pub fn import_settings_from_macho(&mut self, data: &[u8]) -> Result<(), AppleCodesignError> {
939 info!("inferring default signing settings from Mach-O binary");
940
941 let mut seen_identifier = None;
942
943 for macho in MachFile::parse(data)?.into_iter() {
944 let index = macho.index.unwrap_or(0);
945
946 let scope_main = SettingsScope::Main;
947 let scope_index = SettingsScope::MultiArchIndex(index);
948 let scope_arch = SettingsScope::MultiArchCpuType(macho.macho.header.cputype());
949
950 let need_sha1_sha256 = if let Some(targeting) = macho.find_targeting()? {
958 let sha256_version = targeting.platform.sha256_digest_support()?;
959
960 if !sha256_version.matches(&targeting.minimum_os_version) {
961 info!(
962 "activating SHA-1 digests because minimum OS target {} is not {}",
963 targeting.minimum_os_version, sha256_version
964 );
965 true
966 } else {
967 false
968 }
969 } else {
970 info!("activating SHA-1 digests because no platform targeting in Mach-O");
971 true
972 };
973
974 if need_sha1_sha256 {
975 self.set_digest_type(scope_main.clone(), DigestType::Sha1);
980 self.add_extra_digest(scope_main.clone(), DigestType::Sha256);
981 self.extra_digests.remove(&scope_arch);
982 self.extra_digests.remove(&scope_index);
983 }
984
985 if let Some(info_plist) = macho.embedded_info_plist()? {
988 if self.info_plist_data(&scope_main).is_some()
989 || self.info_plist_data(&scope_index).is_some()
990 || self.info_plist_data(&scope_arch).is_some()
991 {
992 info!("using Info.plist data from settings");
993 } else {
994 info!("preserving Info.plist data already present in Mach-O");
995 self.set_info_plist_data(scope_index.clone(), info_plist);
996 }
997 }
998
999 if let Some(sig) = macho.code_signature()? {
1000 if let Some(cd) = sig.code_directory()? {
1001 if self.binary_identifier(&scope_main).is_some()
1002 || self.binary_identifier(&scope_index).is_some()
1003 || self.binary_identifier(&scope_arch).is_some()
1004 {
1005 info!("using binary identifier from settings");
1006 } else if let Some(initial_identifier) = &seen_identifier {
1007 if initial_identifier != cd.ident.as_ref() {
1011 info!("identifiers within Mach-O do not agree (initial: {initial_identifier}, subsequent: {}); reconciling to {initial_identifier}",
1012 cd.ident);
1013 self.set_binary_identifier(scope_index.clone(), initial_identifier);
1014 }
1015 } else {
1016 info!(
1017 "preserving existing binary identifier in Mach-O ({})",
1018 cd.ident.to_string()
1019 );
1020 self.set_binary_identifier(scope_index.clone(), cd.ident.to_string());
1021 seen_identifier = Some(cd.ident.to_string());
1022 }
1023
1024 if self.team_id.contains_key(&scope_main)
1025 || self.team_id.contains_key(&scope_index)
1026 || self.team_id.contains_key(&scope_arch)
1027 {
1028 info!("using team ID from settings");
1029 } else if let Some(team_id) = cd.team_name {
1030 if self.signing_certificate_apple_signed() {
1033 info!(
1034 "preserving team ID in existing Mach-O signature ({})",
1035 team_id
1036 );
1037 self.team_id
1038 .insert(scope_index.clone(), team_id.to_string());
1039 } else {
1040 info!("dropping team ID {} because not signing with an Apple signed certificate", team_id);
1041 }
1042 }
1043
1044 if self.code_signature_flags(&scope_main).is_some()
1045 || self.code_signature_flags(&scope_index).is_some()
1046 || self.code_signature_flags(&scope_arch).is_some()
1047 {
1048 info!("using code signature flags from settings");
1049 } else if !cd.flags.is_empty() {
1050 info!(
1051 "preserving code signature flags in existing Mach-O signature ({:?})",
1052 cd.flags
1053 );
1054 self.set_code_signature_flags(scope_index.clone(), cd.flags);
1055 }
1056
1057 if self.runtime_version(&scope_main).is_some()
1058 || self.runtime_version(&scope_index).is_some()
1059 || self.runtime_version(&scope_arch).is_some()
1060 {
1061 info!("using runtime version from settings");
1062 } else if let Some(version) = cd.runtime {
1063 let version = parse_version_nibbles(version);
1064
1065 info!(
1066 "preserving runtime version in existing Mach-O signature ({})",
1067 version
1068 );
1069 self.set_runtime_version(scope_index.clone(), version);
1070 }
1071 }
1072
1073 if let Some(entitlements) = sig.entitlements()? {
1074 if self.entitlements_plist(&scope_main).is_some()
1075 || self.entitlements_plist(&scope_index).is_some()
1076 || self.entitlements_plist(&scope_arch).is_some()
1077 {
1078 info!("using entitlements from settings");
1079 } else {
1080 info!("preserving existing entitlements in Mach-O");
1081 self.set_entitlements_xml(
1082 SettingsScope::MultiArchIndex(index),
1083 entitlements.as_str(),
1084 )?;
1085 }
1086 }
1087
1088 if let Some(constraints) = sig.launch_constraints_self()? {
1089 if self.launch_constraints_self(&scope_main).is_some()
1090 || self.launch_constraints_self(&scope_index).is_some()
1091 || self.launch_constraints_self(&scope_arch).is_some()
1092 {
1093 info!("using self launch constraints from settings");
1094 } else {
1095 info!("preserving existing self launch constraints in Mach-O");
1096 self.set_launch_constraints_self(
1097 SettingsScope::MultiArchIndex(index),
1098 constraints.parse_encoded_constraints()?,
1099 );
1100 }
1101 }
1102
1103 if let Some(constraints) = sig.launch_constraints_parent()? {
1104 if self.launch_constraints_parent(&scope_main).is_some()
1105 || self.launch_constraints_parent(&scope_index).is_some()
1106 || self.launch_constraints_parent(&scope_arch).is_some()
1107 {
1108 info!("using parent launch constraints from settings");
1109 } else {
1110 info!("preserving existing parent launch constraints in Mach-O");
1111 self.set_launch_constraints_parent(
1112 SettingsScope::MultiArchIndex(index),
1113 constraints.parse_encoded_constraints()?,
1114 );
1115 }
1116 }
1117
1118 if let Some(constraints) = sig.launch_constraints_responsible()? {
1119 if self.launch_constraints_responsible(&scope_main).is_some()
1120 || self.launch_constraints_responsible(&scope_index).is_some()
1121 || self.launch_constraints_responsible(&scope_arch).is_some()
1122 {
1123 info!("using responsible process launch constraints from settings");
1124 } else {
1125 info!(
1126 "preserving existing responsible process launch constraints in Mach-O"
1127 );
1128 self.set_launch_constraints_responsible(
1129 SettingsScope::MultiArchIndex(index),
1130 constraints.parse_encoded_constraints()?,
1131 );
1132 }
1133 }
1134
1135 if let Some(constraints) = sig.library_constraints()? {
1136 if self.library_constraints(&scope_main).is_some()
1137 || self.library_constraints(&scope_index).is_some()
1138 || self.library_constraints(&scope_arch).is_some()
1139 {
1140 info!("using library constraints from settings");
1141 } else {
1142 info!("preserving existing library constraints in Mach-O");
1143 self.set_library_constraints(
1144 SettingsScope::MultiArchIndex(index),
1145 constraints.parse_encoded_constraints()?,
1146 );
1147 }
1148 }
1149 }
1150 }
1151
1152 Ok(())
1153 }
1154
1155 #[must_use]
1157 pub fn as_nested_bundle_settings(&self, bundle_path: &str) -> Self {
1158 self.clone_strip_prefix(
1159 bundle_path,
1160 format!("{bundle_path}/"),
1161 ScopedSetting::inherit_nested_bundle(),
1162 )
1163 }
1164
1165 #[must_use]
1167 pub fn as_bundle_main_executable_settings(&self, path: &str) -> Self {
1168 self.clone_strip_prefix(path, path.to_string(), ScopedSetting::all())
1169 }
1170
1171 #[must_use]
1175 pub fn as_bundle_macho_settings(&self, path: &str) -> Self {
1176 self.clone_strip_prefix(
1177 path,
1178 path.to_string(),
1179 ScopedSetting::inherit_nested_macho(),
1180 )
1181 }
1182
1183 #[must_use]
1189 pub fn as_universal_macho_settings(&self, index: usize, cpu_type: CpuType) -> Self {
1190 self.clone_with_filter_map(|_, key| {
1191 if key == SettingsScope::Main
1192 || key == SettingsScope::MultiArchCpuType(cpu_type)
1193 || key == SettingsScope::MultiArchIndex(index)
1194 {
1195 Some(SettingsScope::Main)
1196 } else {
1197 None
1198 }
1199 })
1200 }
1201
1202 fn clone_strip_prefix(
1205 &self,
1206 main_path: &str,
1207 prefix: String,
1208 preserve_settings: &[ScopedSetting],
1209 ) -> Self {
1210 self.clone_with_filter_map(|setting, key| match key {
1211 SettingsScope::Main => {
1212 if preserve_settings.contains(&setting) {
1213 Some(SettingsScope::Main)
1214 } else {
1215 None
1216 }
1217 }
1218 SettingsScope::Path(path) => {
1219 if path == main_path {
1220 Some(SettingsScope::Main)
1221 } else {
1222 path.strip_prefix(&prefix)
1223 .map(|path| SettingsScope::Path(path.to_string()))
1224 }
1225 }
1226
1227 SettingsScope::MultiArchIndex(index) => {
1231 if preserve_settings.contains(&setting) {
1232 Some(SettingsScope::MultiArchIndex(index))
1233 } else {
1234 None
1235 }
1236 }
1237 SettingsScope::MultiArchCpuType(cpu_type) => {
1238 if preserve_settings.contains(&setting) {
1239 Some(SettingsScope::MultiArchCpuType(cpu_type))
1240 } else {
1241 None
1242 }
1243 }
1244
1245 SettingsScope::PathMultiArchIndex(path, index) => {
1246 if path == main_path {
1247 Some(SettingsScope::MultiArchIndex(index))
1248 } else {
1249 path.strip_prefix(&prefix)
1250 .map(|path| SettingsScope::PathMultiArchIndex(path.to_string(), index))
1251 }
1252 }
1253 SettingsScope::PathMultiArchCpuType(path, cpu_type) => {
1254 if path == main_path {
1255 Some(SettingsScope::MultiArchCpuType(cpu_type))
1256 } else {
1257 path.strip_prefix(&prefix)
1258 .map(|path| SettingsScope::PathMultiArchCpuType(path.to_string(), cpu_type))
1259 }
1260 }
1261 })
1262 }
1263
1264 fn clone_with_filter_map(
1265 &self,
1266 key_map: impl Fn(ScopedSetting, SettingsScope) -> Option<SettingsScope>,
1267 ) -> Self {
1268 Self {
1269 signing_key: self.signing_key.clone(),
1270 certificates: self.certificates.clone(),
1271 time_stamp_url: self.time_stamp_url.clone(),
1272 signing_time: self.signing_time,
1273 team_id: self.team_id.clone(),
1274 path_exclusion_patterns: self.path_exclusion_patterns.clone(),
1275 shallow: self.shallow,
1276 for_notarization: self.for_notarization,
1277 digest_type: self
1278 .digest_type
1279 .clone()
1280 .into_iter()
1281 .filter_map(|(key, value)| {
1282 key_map(ScopedSetting::Digest, key).map(|key| (key, value))
1283 })
1284 .collect::<BTreeMap<_, _>>(),
1285 identifiers: self
1286 .identifiers
1287 .clone()
1288 .into_iter()
1289 .filter_map(|(key, value)| {
1290 key_map(ScopedSetting::BinaryIdentifier, key).map(|key| (key, value))
1291 })
1292 .collect::<BTreeMap<_, _>>(),
1293 entitlements: self
1294 .entitlements
1295 .clone()
1296 .into_iter()
1297 .filter_map(|(key, value)| {
1298 key_map(ScopedSetting::Entitlements, key).map(|key| (key, value))
1299 })
1300 .collect::<BTreeMap<_, _>>(),
1301 designated_requirement: self
1302 .designated_requirement
1303 .clone()
1304 .into_iter()
1305 .filter_map(|(key, value)| {
1306 key_map(ScopedSetting::DesignatedRequirements, key).map(|key| (key, value))
1307 })
1308 .collect::<BTreeMap<_, _>>(),
1309 code_signature_flags: self
1310 .code_signature_flags
1311 .clone()
1312 .into_iter()
1313 .filter_map(|(key, value)| {
1314 key_map(ScopedSetting::CodeSignatureFlags, key).map(|key| (key, value))
1315 })
1316 .collect::<BTreeMap<_, _>>(),
1317 runtime_version: self
1318 .runtime_version
1319 .clone()
1320 .into_iter()
1321 .filter_map(|(key, value)| {
1322 key_map(ScopedSetting::RuntimeVersion, key).map(|key| (key, value))
1323 })
1324 .collect::<BTreeMap<_, _>>(),
1325 info_plist_data: self
1326 .info_plist_data
1327 .clone()
1328 .into_iter()
1329 .filter_map(|(key, value)| {
1330 key_map(ScopedSetting::InfoPlist, key).map(|key| (key, value))
1331 })
1332 .collect::<BTreeMap<_, _>>(),
1333 code_resources_data: self
1334 .code_resources_data
1335 .clone()
1336 .into_iter()
1337 .filter_map(|(key, value)| {
1338 key_map(ScopedSetting::CodeResources, key).map(|key| (key, value))
1339 })
1340 .collect::<BTreeMap<_, _>>(),
1341 extra_digests: self
1342 .extra_digests
1343 .clone()
1344 .into_iter()
1345 .filter_map(|(key, value)| {
1346 key_map(ScopedSetting::ExtraDigests, key).map(|key| (key, value))
1347 })
1348 .collect::<BTreeMap<_, _>>(),
1349 launch_constraints_self: self
1350 .launch_constraints_self
1351 .clone()
1352 .into_iter()
1353 .filter_map(|(key, value)| {
1354 key_map(ScopedSetting::LaunchConstraintsSelf, key).map(|key| (key, value))
1355 })
1356 .collect::<BTreeMap<_, _>>(),
1357 launch_constraints_parent: self
1358 .launch_constraints_parent
1359 .clone()
1360 .into_iter()
1361 .filter_map(|(key, value)| {
1362 key_map(ScopedSetting::LaunchConstraintsParent, key).map(|key| (key, value))
1363 })
1364 .collect::<BTreeMap<_, _>>(),
1365 launch_constraints_responsible: self
1366 .launch_constraints_responsible
1367 .clone()
1368 .into_iter()
1369 .filter_map(|(key, value)| {
1370 key_map(ScopedSetting::LaunchConstraintsResponsible, key)
1371 .map(|key| (key, value))
1372 })
1373 .collect::<BTreeMap<_, _>>(),
1374 library_constraints: self
1375 .library_constraints
1376 .clone()
1377 .into_iter()
1378 .filter_map(|(key, value)| {
1379 key_map(ScopedSetting::LibraryConstraints, key).map(|key| (key, value))
1380 })
1381 .collect::<BTreeMap<_, _>>(),
1382 }
1383 }
1384
1385 pub fn ensure_for_notarization_settings(&self) -> Result<(), AppleCodesignError> {
1389 if !self.for_notarization {
1390 return Ok(());
1391 }
1392
1393 let mut have_error = false;
1394
1395 if let Some((_, cert)) = self.signing_key() {
1396 if !cert.chains_to_apple_root_ca() && !cert.is_test_apple_signed_certificate() {
1397 error!("--for-notarization requires use of an Apple-issued signing certificate; current certificate is not signed by Apple");
1398 error!("hint: use a signing certificate issued by Apple that is signed by an Apple certificate authority");
1399 have_error = true;
1400 }
1401
1402 if !cert.apple_code_signing_extensions().into_iter().any(|e| {
1403 e == CodeSigningCertificateExtension::DeveloperIdApplication
1404 || e == CodeSigningCertificateExtension::DeveloperIdInstaller
1405 || e == CodeSigningCertificateExtension::DeveloperIdKernel {}
1406 }) {
1407 error!("--for-notarization requires use of a Developer ID signing certificate; current certificate doesn't appear to be such a certificate");
1408 error!("hint: use a `Developer ID Application`, `Developer ID Installer`, or `Developer ID Kernel` certificate");
1409 have_error = true;
1410 }
1411
1412 if self.time_stamp_url().is_none() {
1413 error!("--for-notarization requires use of a time-stamp protocol server; none configured");
1414 have_error = true;
1415 }
1416 } else {
1417 error!("--for-notarization requires use of a Developer ID signing certificate; no signing certificate was provided");
1418 have_error = true;
1419 }
1420
1421 if have_error {
1422 Err(AppleCodesignError::ForNotarizationInvalidSettings)
1423 } else {
1424 Ok(())
1425 }
1426 }
1427}
1428
1429#[cfg(test)]
1430mod tests {
1431 use {super::*, indoc::indoc};
1432
1433 const ENTITLEMENTS_XML: &str = indoc! {r#"
1434 <?xml version="1.0" encoding="UTF-8"?>
1435 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1436 <plist version="1.0">
1437 <dict>
1438 <key>application-identifier</key>
1439 <string>appid</string>
1440 <key>com.apple.developer.team-identifier</key>
1441 <string>ABCDEF</string>
1442 </dict>
1443 </plist>
1444 "#};
1445
1446 #[test]
1447 fn parse_settings_scope() {
1448 assert_eq!(
1449 SettingsScope::try_from("@main").unwrap(),
1450 SettingsScope::Main
1451 );
1452 assert_eq!(
1453 SettingsScope::try_from("@0").unwrap(),
1454 SettingsScope::MultiArchIndex(0)
1455 );
1456 assert_eq!(
1457 SettingsScope::try_from("@42").unwrap(),
1458 SettingsScope::MultiArchIndex(42)
1459 );
1460 assert_eq!(
1461 SettingsScope::try_from("@[cpu_type=7]").unwrap(),
1462 SettingsScope::MultiArchCpuType(7)
1463 );
1464 assert_eq!(
1465 SettingsScope::try_from("@[cpu_type=arm]").unwrap(),
1466 SettingsScope::MultiArchCpuType(CPU_TYPE_ARM)
1467 );
1468 assert_eq!(
1469 SettingsScope::try_from("@[cpu_type=arm64]").unwrap(),
1470 SettingsScope::MultiArchCpuType(CPU_TYPE_ARM64)
1471 );
1472 assert_eq!(
1473 SettingsScope::try_from("@[cpu_type=arm64_32]").unwrap(),
1474 SettingsScope::MultiArchCpuType(CPU_TYPE_ARM64_32)
1475 );
1476 assert_eq!(
1477 SettingsScope::try_from("@[cpu_type=x86_64]").unwrap(),
1478 SettingsScope::MultiArchCpuType(CPU_TYPE_X86_64)
1479 );
1480 assert_eq!(
1481 SettingsScope::try_from("foo/bar").unwrap(),
1482 SettingsScope::Path("foo/bar".into())
1483 );
1484 assert_eq!(
1485 SettingsScope::try_from("foo/bar@0").unwrap(),
1486 SettingsScope::PathMultiArchIndex("foo/bar".into(), 0)
1487 );
1488 assert_eq!(
1489 SettingsScope::try_from("foo/bar@[cpu_type=7]").unwrap(),
1490 SettingsScope::PathMultiArchCpuType("foo/bar".into(), 7_u32)
1491 );
1492 }
1493
1494 #[test]
1495 fn as_nested_macho_settings() {
1496 let mut main_settings = SigningSettings::default();
1497 main_settings.set_binary_identifier(SettingsScope::Main, "ident");
1498 main_settings
1499 .set_code_signature_flags(SettingsScope::Main, CodeSignatureFlags::FORCE_EXPIRATION);
1500
1501 main_settings.set_code_signature_flags(
1502 SettingsScope::MultiArchIndex(0),
1503 CodeSignatureFlags::FORCE_HARD,
1504 );
1505 main_settings.set_code_signature_flags(
1506 SettingsScope::MultiArchCpuType(CPU_TYPE_X86_64),
1507 CodeSignatureFlags::RESTRICT,
1508 );
1509 main_settings.set_info_plist_data(SettingsScope::MultiArchIndex(0), b"index_0".to_vec());
1510 main_settings.set_info_plist_data(
1511 SettingsScope::MultiArchCpuType(CPU_TYPE_X86_64),
1512 b"cpu_x86_64".to_vec(),
1513 );
1514
1515 let macho_settings = main_settings.as_universal_macho_settings(0, CPU_TYPE_ARM64);
1516 assert_eq!(
1517 macho_settings.binary_identifier(SettingsScope::Main),
1518 Some("ident")
1519 );
1520 assert_eq!(
1521 macho_settings.code_signature_flags(SettingsScope::Main),
1522 Some(CodeSignatureFlags::FORCE_HARD)
1523 );
1524 assert_eq!(
1525 macho_settings.info_plist_data(SettingsScope::Main),
1526 Some(b"index_0".as_ref())
1527 );
1528
1529 let macho_settings = main_settings.as_universal_macho_settings(0, CPU_TYPE_X86_64);
1530 assert_eq!(
1531 macho_settings.binary_identifier(SettingsScope::Main),
1532 Some("ident")
1533 );
1534 assert_eq!(
1535 macho_settings.code_signature_flags(SettingsScope::Main),
1536 Some(CodeSignatureFlags::RESTRICT)
1537 );
1538 assert_eq!(
1539 macho_settings.info_plist_data(SettingsScope::Main),
1540 Some(b"cpu_x86_64".as_ref())
1541 );
1542 }
1543
1544 #[test]
1545 fn as_bundle_macho_settings() {
1546 let mut main_settings = SigningSettings::default();
1547 main_settings.set_info_plist_data(SettingsScope::Main, b"main".to_vec());
1548 main_settings.set_info_plist_data(
1549 SettingsScope::Path("Contents/MacOS/main".into()),
1550 b"main_exe".to_vec(),
1551 );
1552 main_settings.set_info_plist_data(
1553 SettingsScope::PathMultiArchIndex("Contents/MacOS/main".into(), 0),
1554 b"main_exe_index_0".to_vec(),
1555 );
1556 main_settings.set_info_plist_data(
1557 SettingsScope::PathMultiArchCpuType("Contents/MacOS/main".into(), CPU_TYPE_X86_64),
1558 b"main_exe_x86_64".to_vec(),
1559 );
1560
1561 let macho_settings = main_settings.as_bundle_macho_settings("Contents/MacOS/main");
1562 assert_eq!(
1563 macho_settings.info_plist_data(SettingsScope::Main),
1564 Some(b"main_exe".as_ref())
1565 );
1566 assert_eq!(
1567 macho_settings.info_plist_data,
1568 [
1569 (SettingsScope::Main, b"main_exe".to_vec()),
1570 (
1571 SettingsScope::MultiArchIndex(0),
1572 b"main_exe_index_0".to_vec()
1573 ),
1574 (
1575 SettingsScope::MultiArchCpuType(CPU_TYPE_X86_64),
1576 b"main_exe_x86_64".to_vec()
1577 ),
1578 ]
1579 .iter()
1580 .cloned()
1581 .collect::<BTreeMap<SettingsScope, Vec<u8>>>()
1582 );
1583 }
1584
1585 #[test]
1586 fn as_nested_bundle_settings() {
1587 let mut main_settings = SigningSettings::default();
1588 main_settings.set_info_plist_data(SettingsScope::Main, b"main".to_vec());
1589 main_settings.set_info_plist_data(
1590 SettingsScope::Path("Contents/MacOS/main".into()),
1591 b"main_exe".to_vec(),
1592 );
1593 main_settings.set_info_plist_data(
1594 SettingsScope::Path("Contents/MacOS/nested.app".into()),
1595 b"bundle".to_vec(),
1596 );
1597 main_settings.set_info_plist_data(
1598 SettingsScope::PathMultiArchIndex("Contents/MacOS/nested.app".into(), 0),
1599 b"bundle_index_0".to_vec(),
1600 );
1601 main_settings.set_info_plist_data(
1602 SettingsScope::PathMultiArchCpuType(
1603 "Contents/MacOS/nested.app".into(),
1604 CPU_TYPE_X86_64,
1605 ),
1606 b"bundle_x86_64".to_vec(),
1607 );
1608 main_settings.set_info_plist_data(
1609 SettingsScope::Path("Contents/MacOS/nested.app/Contents/MacOS/nested".into()),
1610 b"nested_main_exe".to_vec(),
1611 );
1612 main_settings.set_info_plist_data(
1613 SettingsScope::PathMultiArchIndex(
1614 "Contents/MacOS/nested.app/Contents/MacOS/nested".into(),
1615 0,
1616 ),
1617 b"nested_main_exe_index_0".to_vec(),
1618 );
1619 main_settings.set_info_plist_data(
1620 SettingsScope::PathMultiArchCpuType(
1621 "Contents/MacOS/nested.app/Contents/MacOS/nested".into(),
1622 CPU_TYPE_X86_64,
1623 ),
1624 b"nested_main_exe_x86_64".to_vec(),
1625 );
1626
1627 let bundle_settings = main_settings.as_nested_bundle_settings("Contents/MacOS/nested.app");
1628 assert_eq!(
1629 bundle_settings.info_plist_data(SettingsScope::Main),
1630 Some(b"bundle".as_ref())
1631 );
1632 assert_eq!(
1633 bundle_settings.info_plist_data(SettingsScope::Path("Contents/MacOS/nested".into())),
1634 Some(b"nested_main_exe".as_ref())
1635 );
1636 assert_eq!(
1637 bundle_settings.info_plist_data,
1638 [
1639 (SettingsScope::Main, b"bundle".to_vec()),
1640 (SettingsScope::MultiArchIndex(0), b"bundle_index_0".to_vec()),
1641 (
1642 SettingsScope::MultiArchCpuType(CPU_TYPE_X86_64),
1643 b"bundle_x86_64".to_vec()
1644 ),
1645 (
1646 SettingsScope::Path("Contents/MacOS/nested".into()),
1647 b"nested_main_exe".to_vec()
1648 ),
1649 (
1650 SettingsScope::PathMultiArchIndex("Contents/MacOS/nested".into(), 0),
1651 b"nested_main_exe_index_0".to_vec()
1652 ),
1653 (
1654 SettingsScope::PathMultiArchCpuType(
1655 "Contents/MacOS/nested".into(),
1656 CPU_TYPE_X86_64
1657 ),
1658 b"nested_main_exe_x86_64".to_vec()
1659 ),
1660 ]
1661 .iter()
1662 .cloned()
1663 .collect::<BTreeMap<SettingsScope, Vec<u8>>>()
1664 );
1665 }
1666
1667 #[test]
1668 fn entitlements_handling() -> Result<(), AppleCodesignError> {
1669 let mut settings = SigningSettings::default();
1670 settings.set_entitlements_xml(SettingsScope::Main, ENTITLEMENTS_XML)?;
1671
1672 let s = settings.entitlements_xml(SettingsScope::Main)?;
1673 assert_eq!(s, Some("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>application-identifier</key>\n\t<string>appid</string>\n\t<key>com.apple.developer.team-identifier</key>\n\t<string>ABCDEF</string>\n</dict>\n</plist>".into()));
1674
1675 Ok(())
1676 }
1677
1678 #[test]
1679 fn for_notarization_handling() -> Result<(), AppleCodesignError> {
1680 let mut settings = SigningSettings::default();
1681 settings.set_for_notarization(true);
1682
1683 assert_eq!(
1684 settings.code_signature_flags(SettingsScope::Main),
1685 Some(CodeSignatureFlags::RUNTIME)
1686 );
1687
1688 assert_eq!(
1689 settings
1690 .as_bundle_macho_settings("")
1691 .code_signature_flags(SettingsScope::Main),
1692 Some(CodeSignatureFlags::RUNTIME)
1693 );
1694
1695 Ok(())
1696 }
1697}