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::{MachFile, parse_version_nibbles},
17 },
18 glob::Pattern,
19 goblin::mach::cputype::{
20 CPU_TYPE_ARM, CPU_TYPE_ARM64, CPU_TYPE_ARM64_32, CPU_TYPE_X86_64, CpuType,
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 {
1011 info!(
1012 "identifiers within Mach-O do not agree (initial: {initial_identifier}, subsequent: {}); reconciling to {initial_identifier}",
1013 cd.ident
1014 );
1015 self.set_binary_identifier(scope_index.clone(), initial_identifier);
1016 }
1017 } else {
1018 info!(
1019 "preserving existing binary identifier in Mach-O ({})",
1020 cd.ident
1021 );
1022 self.set_binary_identifier(scope_index.clone(), cd.ident.to_string());
1023 seen_identifier = Some(cd.ident.to_string());
1024 }
1025
1026 if self.team_id.contains_key(&scope_main)
1027 || self.team_id.contains_key(&scope_index)
1028 || self.team_id.contains_key(&scope_arch)
1029 {
1030 info!("using team ID from settings");
1031 } else if let Some(team_id) = cd.team_name {
1032 if self.signing_certificate_apple_signed() {
1035 info!(
1036 "preserving team ID in existing Mach-O signature ({})",
1037 team_id
1038 );
1039 self.team_id
1040 .insert(scope_index.clone(), team_id.to_string());
1041 } else {
1042 info!(
1043 "dropping team ID {} because not signing with an Apple signed certificate",
1044 team_id
1045 );
1046 }
1047 }
1048
1049 if self.code_signature_flags(&scope_main).is_some()
1050 || self.code_signature_flags(&scope_index).is_some()
1051 || self.code_signature_flags(&scope_arch).is_some()
1052 {
1053 info!("using code signature flags from settings");
1054 } else if !cd.flags.is_empty() {
1055 info!(
1056 "preserving code signature flags in existing Mach-O signature ({:?})",
1057 cd.flags
1058 );
1059 self.set_code_signature_flags(scope_index.clone(), cd.flags);
1060 }
1061
1062 if self.runtime_version(&scope_main).is_some()
1063 || self.runtime_version(&scope_index).is_some()
1064 || self.runtime_version(&scope_arch).is_some()
1065 {
1066 info!("using runtime version from settings");
1067 } else if let Some(version) = cd.runtime {
1068 let version = parse_version_nibbles(version);
1069
1070 info!(
1071 "preserving runtime version in existing Mach-O signature ({})",
1072 version
1073 );
1074 self.set_runtime_version(scope_index.clone(), version);
1075 }
1076 }
1077
1078 if let Some(entitlements) = sig.entitlements()? {
1079 if self.entitlements_plist(&scope_main).is_some()
1080 || self.entitlements_plist(&scope_index).is_some()
1081 || self.entitlements_plist(&scope_arch).is_some()
1082 {
1083 info!("using entitlements from settings");
1084 } else {
1085 info!("preserving existing entitlements in Mach-O");
1086 self.set_entitlements_xml(
1087 SettingsScope::MultiArchIndex(index),
1088 entitlements.as_str(),
1089 )?;
1090 }
1091 }
1092
1093 if let Some(constraints) = sig.launch_constraints_self()? {
1094 if self.launch_constraints_self(&scope_main).is_some()
1095 || self.launch_constraints_self(&scope_index).is_some()
1096 || self.launch_constraints_self(&scope_arch).is_some()
1097 {
1098 info!("using self launch constraints from settings");
1099 } else {
1100 info!("preserving existing self launch constraints in Mach-O");
1101 self.set_launch_constraints_self(
1102 SettingsScope::MultiArchIndex(index),
1103 constraints.parse_encoded_constraints()?,
1104 );
1105 }
1106 }
1107
1108 if let Some(constraints) = sig.launch_constraints_parent()? {
1109 if self.launch_constraints_parent(&scope_main).is_some()
1110 || self.launch_constraints_parent(&scope_index).is_some()
1111 || self.launch_constraints_parent(&scope_arch).is_some()
1112 {
1113 info!("using parent launch constraints from settings");
1114 } else {
1115 info!("preserving existing parent launch constraints in Mach-O");
1116 self.set_launch_constraints_parent(
1117 SettingsScope::MultiArchIndex(index),
1118 constraints.parse_encoded_constraints()?,
1119 );
1120 }
1121 }
1122
1123 if let Some(constraints) = sig.launch_constraints_responsible()? {
1124 if self.launch_constraints_responsible(&scope_main).is_some()
1125 || self.launch_constraints_responsible(&scope_index).is_some()
1126 || self.launch_constraints_responsible(&scope_arch).is_some()
1127 {
1128 info!("using responsible process launch constraints from settings");
1129 } else {
1130 info!(
1131 "preserving existing responsible process launch constraints in Mach-O"
1132 );
1133 self.set_launch_constraints_responsible(
1134 SettingsScope::MultiArchIndex(index),
1135 constraints.parse_encoded_constraints()?,
1136 );
1137 }
1138 }
1139
1140 if let Some(constraints) = sig.library_constraints()? {
1141 if self.library_constraints(&scope_main).is_some()
1142 || self.library_constraints(&scope_index).is_some()
1143 || self.library_constraints(&scope_arch).is_some()
1144 {
1145 info!("using library constraints from settings");
1146 } else {
1147 info!("preserving existing library constraints in Mach-O");
1148 self.set_library_constraints(
1149 SettingsScope::MultiArchIndex(index),
1150 constraints.parse_encoded_constraints()?,
1151 );
1152 }
1153 }
1154 }
1155 }
1156
1157 Ok(())
1158 }
1159
1160 #[must_use]
1162 pub fn as_nested_bundle_settings(&self, bundle_path: &str) -> Self {
1163 self.clone_strip_prefix(
1164 bundle_path,
1165 format!("{bundle_path}/"),
1166 ScopedSetting::inherit_nested_bundle(),
1167 )
1168 }
1169
1170 #[must_use]
1172 pub fn as_bundle_main_executable_settings(&self, path: &str) -> Self {
1173 self.clone_strip_prefix(path, path.to_string(), ScopedSetting::all())
1174 }
1175
1176 #[must_use]
1180 pub fn as_bundle_macho_settings(&self, path: &str) -> Self {
1181 self.clone_strip_prefix(
1182 path,
1183 path.to_string(),
1184 ScopedSetting::inherit_nested_macho(),
1185 )
1186 }
1187
1188 #[must_use]
1194 pub fn as_universal_macho_settings(&self, index: usize, cpu_type: CpuType) -> Self {
1195 self.clone_with_filter_map(|_, key| {
1196 if key == SettingsScope::Main
1197 || key == SettingsScope::MultiArchCpuType(cpu_type)
1198 || key == SettingsScope::MultiArchIndex(index)
1199 {
1200 Some(SettingsScope::Main)
1201 } else {
1202 None
1203 }
1204 })
1205 }
1206
1207 fn clone_strip_prefix(
1210 &self,
1211 main_path: &str,
1212 prefix: String,
1213 preserve_settings: &[ScopedSetting],
1214 ) -> Self {
1215 self.clone_with_filter_map(|setting, key| match key {
1216 SettingsScope::Main => {
1217 if preserve_settings.contains(&setting) {
1218 Some(SettingsScope::Main)
1219 } else {
1220 None
1221 }
1222 }
1223 SettingsScope::Path(path) => {
1224 if path == main_path {
1225 Some(SettingsScope::Main)
1226 } else {
1227 path.strip_prefix(&prefix)
1228 .map(|path| SettingsScope::Path(path.to_string()))
1229 }
1230 }
1231
1232 SettingsScope::MultiArchIndex(index) => {
1236 if preserve_settings.contains(&setting) {
1237 Some(SettingsScope::MultiArchIndex(index))
1238 } else {
1239 None
1240 }
1241 }
1242 SettingsScope::MultiArchCpuType(cpu_type) => {
1243 if preserve_settings.contains(&setting) {
1244 Some(SettingsScope::MultiArchCpuType(cpu_type))
1245 } else {
1246 None
1247 }
1248 }
1249
1250 SettingsScope::PathMultiArchIndex(path, index) => {
1251 if path == main_path {
1252 Some(SettingsScope::MultiArchIndex(index))
1253 } else {
1254 path.strip_prefix(&prefix)
1255 .map(|path| SettingsScope::PathMultiArchIndex(path.to_string(), index))
1256 }
1257 }
1258 SettingsScope::PathMultiArchCpuType(path, cpu_type) => {
1259 if path == main_path {
1260 Some(SettingsScope::MultiArchCpuType(cpu_type))
1261 } else {
1262 path.strip_prefix(&prefix)
1263 .map(|path| SettingsScope::PathMultiArchCpuType(path.to_string(), cpu_type))
1264 }
1265 }
1266 })
1267 }
1268
1269 fn clone_with_filter_map(
1270 &self,
1271 key_map: impl Fn(ScopedSetting, SettingsScope) -> Option<SettingsScope>,
1272 ) -> Self {
1273 Self {
1274 signing_key: self.signing_key.clone(),
1275 certificates: self.certificates.clone(),
1276 time_stamp_url: self.time_stamp_url.clone(),
1277 signing_time: self.signing_time,
1278 team_id: self.team_id.clone(),
1279 path_exclusion_patterns: self.path_exclusion_patterns.clone(),
1280 shallow: self.shallow,
1281 for_notarization: self.for_notarization,
1282 digest_type: self
1283 .digest_type
1284 .clone()
1285 .into_iter()
1286 .filter_map(|(key, value)| {
1287 key_map(ScopedSetting::Digest, key).map(|key| (key, value))
1288 })
1289 .collect::<BTreeMap<_, _>>(),
1290 identifiers: self
1291 .identifiers
1292 .clone()
1293 .into_iter()
1294 .filter_map(|(key, value)| {
1295 key_map(ScopedSetting::BinaryIdentifier, key).map(|key| (key, value))
1296 })
1297 .collect::<BTreeMap<_, _>>(),
1298 entitlements: self
1299 .entitlements
1300 .clone()
1301 .into_iter()
1302 .filter_map(|(key, value)| {
1303 key_map(ScopedSetting::Entitlements, key).map(|key| (key, value))
1304 })
1305 .collect::<BTreeMap<_, _>>(),
1306 designated_requirement: self
1307 .designated_requirement
1308 .clone()
1309 .into_iter()
1310 .filter_map(|(key, value)| {
1311 key_map(ScopedSetting::DesignatedRequirements, key).map(|key| (key, value))
1312 })
1313 .collect::<BTreeMap<_, _>>(),
1314 code_signature_flags: self
1315 .code_signature_flags
1316 .clone()
1317 .into_iter()
1318 .filter_map(|(key, value)| {
1319 key_map(ScopedSetting::CodeSignatureFlags, key).map(|key| (key, value))
1320 })
1321 .collect::<BTreeMap<_, _>>(),
1322 runtime_version: self
1323 .runtime_version
1324 .clone()
1325 .into_iter()
1326 .filter_map(|(key, value)| {
1327 key_map(ScopedSetting::RuntimeVersion, key).map(|key| (key, value))
1328 })
1329 .collect::<BTreeMap<_, _>>(),
1330 info_plist_data: self
1331 .info_plist_data
1332 .clone()
1333 .into_iter()
1334 .filter_map(|(key, value)| {
1335 key_map(ScopedSetting::InfoPlist, key).map(|key| (key, value))
1336 })
1337 .collect::<BTreeMap<_, _>>(),
1338 code_resources_data: self
1339 .code_resources_data
1340 .clone()
1341 .into_iter()
1342 .filter_map(|(key, value)| {
1343 key_map(ScopedSetting::CodeResources, key).map(|key| (key, value))
1344 })
1345 .collect::<BTreeMap<_, _>>(),
1346 extra_digests: self
1347 .extra_digests
1348 .clone()
1349 .into_iter()
1350 .filter_map(|(key, value)| {
1351 key_map(ScopedSetting::ExtraDigests, key).map(|key| (key, value))
1352 })
1353 .collect::<BTreeMap<_, _>>(),
1354 launch_constraints_self: self
1355 .launch_constraints_self
1356 .clone()
1357 .into_iter()
1358 .filter_map(|(key, value)| {
1359 key_map(ScopedSetting::LaunchConstraintsSelf, key).map(|key| (key, value))
1360 })
1361 .collect::<BTreeMap<_, _>>(),
1362 launch_constraints_parent: self
1363 .launch_constraints_parent
1364 .clone()
1365 .into_iter()
1366 .filter_map(|(key, value)| {
1367 key_map(ScopedSetting::LaunchConstraintsParent, key).map(|key| (key, value))
1368 })
1369 .collect::<BTreeMap<_, _>>(),
1370 launch_constraints_responsible: self
1371 .launch_constraints_responsible
1372 .clone()
1373 .into_iter()
1374 .filter_map(|(key, value)| {
1375 key_map(ScopedSetting::LaunchConstraintsResponsible, key)
1376 .map(|key| (key, value))
1377 })
1378 .collect::<BTreeMap<_, _>>(),
1379 library_constraints: self
1380 .library_constraints
1381 .clone()
1382 .into_iter()
1383 .filter_map(|(key, value)| {
1384 key_map(ScopedSetting::LibraryConstraints, key).map(|key| (key, value))
1385 })
1386 .collect::<BTreeMap<_, _>>(),
1387 }
1388 }
1389
1390 pub fn ensure_for_notarization_settings(&self) -> Result<(), AppleCodesignError> {
1394 if !self.for_notarization {
1395 return Ok(());
1396 }
1397
1398 let mut have_error = false;
1399
1400 if let Some((_, cert)) = self.signing_key() {
1401 if !cert.chains_to_apple_root_ca() && !cert.is_test_apple_signed_certificate() {
1402 error!(
1403 "--for-notarization requires use of an Apple-issued signing certificate; current certificate is not signed by Apple"
1404 );
1405 error!(
1406 "hint: use a signing certificate issued by Apple that is signed by an Apple certificate authority"
1407 );
1408 have_error = true;
1409 }
1410
1411 if !cert.apple_code_signing_extensions().into_iter().any(|e| {
1412 e == CodeSigningCertificateExtension::DeveloperIdApplication
1413 || e == CodeSigningCertificateExtension::DeveloperIdInstaller
1414 || e == CodeSigningCertificateExtension::DeveloperIdKernel {}
1415 }) {
1416 error!(
1417 "--for-notarization requires use of a Developer ID signing certificate; current certificate doesn't appear to be such a certificate"
1418 );
1419 error!(
1420 "hint: use a `Developer ID Application`, `Developer ID Installer`, or `Developer ID Kernel` certificate"
1421 );
1422 have_error = true;
1423 }
1424
1425 if self.time_stamp_url().is_none() {
1426 error!(
1427 "--for-notarization requires use of a time-stamp protocol server; none configured"
1428 );
1429 have_error = true;
1430 }
1431 } else {
1432 error!(
1433 "--for-notarization requires use of a Developer ID signing certificate; no signing certificate was provided"
1434 );
1435 have_error = true;
1436 }
1437
1438 if have_error {
1439 Err(AppleCodesignError::ForNotarizationInvalidSettings)
1440 } else {
1441 Ok(())
1442 }
1443 }
1444}
1445
1446#[cfg(test)]
1447mod tests {
1448 use {super::*, indoc::indoc};
1449
1450 const ENTITLEMENTS_XML: &str = indoc! {r#"
1451 <?xml version="1.0" encoding="UTF-8"?>
1452 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1453 <plist version="1.0">
1454 <dict>
1455 <key>application-identifier</key>
1456 <string>appid</string>
1457 <key>com.apple.developer.team-identifier</key>
1458 <string>ABCDEF</string>
1459 </dict>
1460 </plist>
1461 "#};
1462
1463 #[test]
1464 fn parse_settings_scope() {
1465 assert_eq!(
1466 SettingsScope::try_from("@main").unwrap(),
1467 SettingsScope::Main
1468 );
1469 assert_eq!(
1470 SettingsScope::try_from("@0").unwrap(),
1471 SettingsScope::MultiArchIndex(0)
1472 );
1473 assert_eq!(
1474 SettingsScope::try_from("@42").unwrap(),
1475 SettingsScope::MultiArchIndex(42)
1476 );
1477 assert_eq!(
1478 SettingsScope::try_from("@[cpu_type=7]").unwrap(),
1479 SettingsScope::MultiArchCpuType(7)
1480 );
1481 assert_eq!(
1482 SettingsScope::try_from("@[cpu_type=arm]").unwrap(),
1483 SettingsScope::MultiArchCpuType(CPU_TYPE_ARM)
1484 );
1485 assert_eq!(
1486 SettingsScope::try_from("@[cpu_type=arm64]").unwrap(),
1487 SettingsScope::MultiArchCpuType(CPU_TYPE_ARM64)
1488 );
1489 assert_eq!(
1490 SettingsScope::try_from("@[cpu_type=arm64_32]").unwrap(),
1491 SettingsScope::MultiArchCpuType(CPU_TYPE_ARM64_32)
1492 );
1493 assert_eq!(
1494 SettingsScope::try_from("@[cpu_type=x86_64]").unwrap(),
1495 SettingsScope::MultiArchCpuType(CPU_TYPE_X86_64)
1496 );
1497 assert_eq!(
1498 SettingsScope::try_from("foo/bar").unwrap(),
1499 SettingsScope::Path("foo/bar".into())
1500 );
1501 assert_eq!(
1502 SettingsScope::try_from("foo/bar@0").unwrap(),
1503 SettingsScope::PathMultiArchIndex("foo/bar".into(), 0)
1504 );
1505 assert_eq!(
1506 SettingsScope::try_from("foo/bar@[cpu_type=7]").unwrap(),
1507 SettingsScope::PathMultiArchCpuType("foo/bar".into(), 7_u32)
1508 );
1509 }
1510
1511 #[test]
1512 fn as_nested_macho_settings() {
1513 let mut main_settings = SigningSettings::default();
1514 main_settings.set_binary_identifier(SettingsScope::Main, "ident");
1515 main_settings
1516 .set_code_signature_flags(SettingsScope::Main, CodeSignatureFlags::FORCE_EXPIRATION);
1517
1518 main_settings.set_code_signature_flags(
1519 SettingsScope::MultiArchIndex(0),
1520 CodeSignatureFlags::FORCE_HARD,
1521 );
1522 main_settings.set_code_signature_flags(
1523 SettingsScope::MultiArchCpuType(CPU_TYPE_X86_64),
1524 CodeSignatureFlags::RESTRICT,
1525 );
1526 main_settings.set_info_plist_data(SettingsScope::MultiArchIndex(0), b"index_0".to_vec());
1527 main_settings.set_info_plist_data(
1528 SettingsScope::MultiArchCpuType(CPU_TYPE_X86_64),
1529 b"cpu_x86_64".to_vec(),
1530 );
1531
1532 let macho_settings = main_settings.as_universal_macho_settings(0, CPU_TYPE_ARM64);
1533 assert_eq!(
1534 macho_settings.binary_identifier(SettingsScope::Main),
1535 Some("ident")
1536 );
1537 assert_eq!(
1538 macho_settings.code_signature_flags(SettingsScope::Main),
1539 Some(CodeSignatureFlags::FORCE_HARD)
1540 );
1541 assert_eq!(
1542 macho_settings.info_plist_data(SettingsScope::Main),
1543 Some(b"index_0".as_ref())
1544 );
1545
1546 let macho_settings = main_settings.as_universal_macho_settings(0, CPU_TYPE_X86_64);
1547 assert_eq!(
1548 macho_settings.binary_identifier(SettingsScope::Main),
1549 Some("ident")
1550 );
1551 assert_eq!(
1552 macho_settings.code_signature_flags(SettingsScope::Main),
1553 Some(CodeSignatureFlags::RESTRICT)
1554 );
1555 assert_eq!(
1556 macho_settings.info_plist_data(SettingsScope::Main),
1557 Some(b"cpu_x86_64".as_ref())
1558 );
1559 }
1560
1561 #[test]
1562 fn as_bundle_macho_settings() {
1563 let mut main_settings = SigningSettings::default();
1564 main_settings.set_info_plist_data(SettingsScope::Main, b"main".to_vec());
1565 main_settings.set_info_plist_data(
1566 SettingsScope::Path("Contents/MacOS/main".into()),
1567 b"main_exe".to_vec(),
1568 );
1569 main_settings.set_info_plist_data(
1570 SettingsScope::PathMultiArchIndex("Contents/MacOS/main".into(), 0),
1571 b"main_exe_index_0".to_vec(),
1572 );
1573 main_settings.set_info_plist_data(
1574 SettingsScope::PathMultiArchCpuType("Contents/MacOS/main".into(), CPU_TYPE_X86_64),
1575 b"main_exe_x86_64".to_vec(),
1576 );
1577
1578 let macho_settings = main_settings.as_bundle_macho_settings("Contents/MacOS/main");
1579 assert_eq!(
1580 macho_settings.info_plist_data(SettingsScope::Main),
1581 Some(b"main_exe".as_ref())
1582 );
1583 assert_eq!(
1584 macho_settings.info_plist_data,
1585 [
1586 (SettingsScope::Main, b"main_exe".to_vec()),
1587 (
1588 SettingsScope::MultiArchIndex(0),
1589 b"main_exe_index_0".to_vec()
1590 ),
1591 (
1592 SettingsScope::MultiArchCpuType(CPU_TYPE_X86_64),
1593 b"main_exe_x86_64".to_vec()
1594 ),
1595 ]
1596 .iter()
1597 .cloned()
1598 .collect::<BTreeMap<SettingsScope, Vec<u8>>>()
1599 );
1600 }
1601
1602 #[test]
1603 fn as_nested_bundle_settings() {
1604 let mut main_settings = SigningSettings::default();
1605 main_settings.set_info_plist_data(SettingsScope::Main, b"main".to_vec());
1606 main_settings.set_info_plist_data(
1607 SettingsScope::Path("Contents/MacOS/main".into()),
1608 b"main_exe".to_vec(),
1609 );
1610 main_settings.set_info_plist_data(
1611 SettingsScope::Path("Contents/MacOS/nested.app".into()),
1612 b"bundle".to_vec(),
1613 );
1614 main_settings.set_info_plist_data(
1615 SettingsScope::PathMultiArchIndex("Contents/MacOS/nested.app".into(), 0),
1616 b"bundle_index_0".to_vec(),
1617 );
1618 main_settings.set_info_plist_data(
1619 SettingsScope::PathMultiArchCpuType(
1620 "Contents/MacOS/nested.app".into(),
1621 CPU_TYPE_X86_64,
1622 ),
1623 b"bundle_x86_64".to_vec(),
1624 );
1625 main_settings.set_info_plist_data(
1626 SettingsScope::Path("Contents/MacOS/nested.app/Contents/MacOS/nested".into()),
1627 b"nested_main_exe".to_vec(),
1628 );
1629 main_settings.set_info_plist_data(
1630 SettingsScope::PathMultiArchIndex(
1631 "Contents/MacOS/nested.app/Contents/MacOS/nested".into(),
1632 0,
1633 ),
1634 b"nested_main_exe_index_0".to_vec(),
1635 );
1636 main_settings.set_info_plist_data(
1637 SettingsScope::PathMultiArchCpuType(
1638 "Contents/MacOS/nested.app/Contents/MacOS/nested".into(),
1639 CPU_TYPE_X86_64,
1640 ),
1641 b"nested_main_exe_x86_64".to_vec(),
1642 );
1643
1644 let bundle_settings = main_settings.as_nested_bundle_settings("Contents/MacOS/nested.app");
1645 assert_eq!(
1646 bundle_settings.info_plist_data(SettingsScope::Main),
1647 Some(b"bundle".as_ref())
1648 );
1649 assert_eq!(
1650 bundle_settings.info_plist_data(SettingsScope::Path("Contents/MacOS/nested".into())),
1651 Some(b"nested_main_exe".as_ref())
1652 );
1653 assert_eq!(
1654 bundle_settings.info_plist_data,
1655 [
1656 (SettingsScope::Main, b"bundle".to_vec()),
1657 (SettingsScope::MultiArchIndex(0), b"bundle_index_0".to_vec()),
1658 (
1659 SettingsScope::MultiArchCpuType(CPU_TYPE_X86_64),
1660 b"bundle_x86_64".to_vec()
1661 ),
1662 (
1663 SettingsScope::Path("Contents/MacOS/nested".into()),
1664 b"nested_main_exe".to_vec()
1665 ),
1666 (
1667 SettingsScope::PathMultiArchIndex("Contents/MacOS/nested".into(), 0),
1668 b"nested_main_exe_index_0".to_vec()
1669 ),
1670 (
1671 SettingsScope::PathMultiArchCpuType(
1672 "Contents/MacOS/nested".into(),
1673 CPU_TYPE_X86_64
1674 ),
1675 b"nested_main_exe_x86_64".to_vec()
1676 ),
1677 ]
1678 .iter()
1679 .cloned()
1680 .collect::<BTreeMap<SettingsScope, Vec<u8>>>()
1681 );
1682 }
1683
1684 #[test]
1685 fn entitlements_handling() -> Result<(), AppleCodesignError> {
1686 let mut settings = SigningSettings::default();
1687 settings.set_entitlements_xml(SettingsScope::Main, ENTITLEMENTS_XML)?;
1688
1689 let s = settings.entitlements_xml(SettingsScope::Main)?;
1690 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()));
1691
1692 Ok(())
1693 }
1694
1695 #[test]
1696 fn for_notarization_handling() -> Result<(), AppleCodesignError> {
1697 let mut settings = SigningSettings::default();
1698 settings.set_for_notarization(true);
1699
1700 assert_eq!(
1701 settings.code_signature_flags(SettingsScope::Main),
1702 Some(CodeSignatureFlags::RUNTIME)
1703 );
1704
1705 assert_eq!(
1706 settings
1707 .as_bundle_macho_settings("")
1708 .code_signature_flags(SettingsScope::Main),
1709 Some(CodeSignatureFlags::RUNTIME)
1710 );
1711
1712 Ok(())
1713 }
1714}