1use {
23 crate::{
24 certificate::{
25 AppleCertificate, CertificateAuthorityExtension, CodeSigningCertificateExtension,
26 },
27 code_requirement::{CodeRequirementExpression, CodeRequirementMatchExpression},
28 error::AppleCodesignError,
29 },
30 once_cell::sync::Lazy,
31 std::ops::Deref,
32 x509_certificate::CapturedX509Certificate,
33};
34
35static POLICY_MAC_DEVELOPER_ID: Lazy<CodeRequirementExpression<'static>> = Lazy::new(|| {
40 CodeRequirementExpression::And(
41 Box::new(CodeRequirementExpression::And(
42 Box::new(CodeRequirementExpression::AnchorAppleGeneric),
43 Box::new(CodeRequirementExpression::CertificateGeneric(
44 1,
45 CertificateAuthorityExtension::DeveloperId.as_oid(),
46 CodeRequirementMatchExpression::Exists,
47 )),
48 )),
49 Box::new(CodeRequirementExpression::Or(
50 Box::new(CodeRequirementExpression::CertificateGeneric(
51 0,
52 CodeSigningCertificateExtension::DeveloperIdInstaller.as_oid(),
53 CodeRequirementMatchExpression::Exists,
54 )),
55 Box::new(CodeRequirementExpression::CertificateGeneric(
56 0,
57 CodeSigningCertificateExtension::DeveloperIdApplication.as_oid(),
58 CodeRequirementMatchExpression::Exists,
59 )),
60 )),
61 )
62});
63
64static POLICY_NOTARIZED_EXECUTABLE: Lazy<CodeRequirementExpression<'static>> = Lazy::new(|| {
70 CodeRequirementExpression::And(
71 Box::new(CodeRequirementExpression::And(
72 Box::new(CodeRequirementExpression::And(
73 Box::new(CodeRequirementExpression::AnchorAppleGeneric),
74 Box::new(CodeRequirementExpression::CertificateGeneric(
75 1,
76 CertificateAuthorityExtension::DeveloperId.as_oid(),
77 CodeRequirementMatchExpression::Exists,
78 )),
79 )),
80 Box::new(CodeRequirementExpression::CertificateGeneric(
81 0,
82 CodeSigningCertificateExtension::DeveloperIdApplication.as_oid(),
83 CodeRequirementMatchExpression::Exists,
84 )),
85 )),
86 Box::new(CodeRequirementExpression::Notarized),
87 )
88});
89
90static POLICY_NOTARIZED_INSTALLER: Lazy<CodeRequirementExpression<'static>> = Lazy::new(|| {
96 CodeRequirementExpression::And(
97 Box::new(CodeRequirementExpression::And(
98 Box::new(CodeRequirementExpression::And(
99 Box::new(CodeRequirementExpression::AnchorAppleGeneric),
100 Box::new(CodeRequirementExpression::CertificateGeneric(
101 1,
102 CertificateAuthorityExtension::DeveloperId.as_oid(),
103 CodeRequirementMatchExpression::Exists,
104 )),
105 )),
106 Box::new(CodeRequirementExpression::Or(
107 Box::new(CodeRequirementExpression::CertificateGeneric(
108 0,
109 CodeSigningCertificateExtension::DeveloperIdInstaller.as_oid(),
110 CodeRequirementMatchExpression::Exists,
111 )),
112 Box::new(CodeRequirementExpression::CertificateGeneric(
113 0,
114 CodeSigningCertificateExtension::DeveloperIdApplication.as_oid(),
115 CodeRequirementMatchExpression::Exists,
116 )),
117 )),
118 )),
119 Box::new(CodeRequirementExpression::Notarized),
120 )
121});
122
123#[allow(clippy::enum_variant_names)]
132#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, clap::ValueEnum)]
133pub enum ExecutionPolicy {
134 DeveloperIdSigned,
142
143 DeveloperIdNotarizedExecutable,
149
150 DeveloperIdNotarizedInstaller,
156}
157
158impl Deref for ExecutionPolicy {
159 type Target = CodeRequirementExpression<'static>;
160
161 fn deref(&self) -> &Self::Target {
162 match self {
163 Self::DeveloperIdSigned => POLICY_MAC_DEVELOPER_ID.deref(),
164 Self::DeveloperIdNotarizedExecutable => POLICY_NOTARIZED_EXECUTABLE.deref(),
165 Self::DeveloperIdNotarizedInstaller => POLICY_NOTARIZED_INSTALLER.deref(),
166 }
167 }
168}
169
170impl TryFrom<&str> for ExecutionPolicy {
171 type Error = AppleCodesignError;
172
173 fn try_from(s: &str) -> Result<Self, Self::Error> {
174 match s {
175 "developer-id-signed" => Ok(Self::DeveloperIdSigned),
176 "developer-id-notarized-executable" => Ok(Self::DeveloperIdNotarizedExecutable),
177 "developer-id-notarized-installer" => Ok(Self::DeveloperIdNotarizedInstaller),
178 _ => Err(AppleCodesignError::UnknownPolicy(s.to_string())),
179 }
180 }
181}
182
183pub fn derive_designated_requirements(
192 signing_cert: &CapturedX509Certificate,
193 chain: &[CapturedX509Certificate],
194 identifier: Option<String>,
195) -> Result<CodeRequirementExpression<'static>, AppleCodesignError> {
196 let expr = if signing_cert.chains_to_apple_root_ca() {
197 let apple_chain = signing_cert.apple_issuing_chain();
198
199 assert!(
200 !apple_chain.is_empty(),
201 "we should be able to resolve the Apple CA chain if chains_to_apple_root_ca() is true"
202 );
203
204 let first = apple_chain[0];
205
206 if first
207 .apple_ca_extensions()
208 .into_iter()
209 .any(|ext| ext == CertificateAuthorityExtension::AppleWorldwideDeveloperRelations)
210 {
211 let cn = signing_cert.subject_common_name().ok_or_else(|| {
212 AppleCodesignError::PolicyFormulationError(
213 "certificate common name not available".to_string(),
214 )
215 })?;
216 worldwide_developer_relations_signed_expression(cn)
217 } else if first
218 .apple_ca_extensions()
219 .into_iter()
220 .any(|ext| ext == CertificateAuthorityExtension::DeveloperId)
221 {
222 let team_id = signing_cert.apple_team_id().ok_or_else(|| {
223 AppleCodesignError::PolicyFormulationError(
224 "could not find team identifier in signing certificate".to_string(),
225 )
226 })?;
227
228 developer_id_signed_expression(team_id)
229 } else {
230 CodeRequirementExpression::AnchorApple
231 }
232 } else {
233 let chain = signing_cert
235 .resolve_signing_chain(chain.iter())
236 .into_iter()
237 .cloned()
238 .collect::<Vec<_>>();
239
240 non_apple_signed_expression(signing_cert, &chain)?
241 };
242
243 Ok(if let Some(identifier) = identifier {
245 CodeRequirementExpression::And(
246 Box::new(CodeRequirementExpression::Identifier(identifier.into())),
247 Box::new(expr),
248 )
249 } else {
250 expr
251 })
252}
253
254pub fn developer_id_signed_expression(
259 team_id: impl ToString,
260) -> CodeRequirementExpression<'static> {
261 CodeRequirementExpression::And(
262 Box::new(CodeRequirementExpression::AnchorAppleGeneric),
264 Box::new(CodeRequirementExpression::And(
265 Box::new(CodeRequirementExpression::CertificateGeneric(
267 1,
268 CertificateAuthorityExtension::DeveloperId.as_oid(),
269 CodeRequirementMatchExpression::Exists,
270 )),
271 Box::new(CodeRequirementExpression::And(
272 Box::new(CodeRequirementExpression::CertificateGeneric(
274 0,
275 CodeSigningCertificateExtension::DeveloperIdApplication.as_oid(),
276 CodeRequirementMatchExpression::Exists,
277 )),
278 Box::new(CodeRequirementExpression::CertificateField(
280 0,
281 "subject.OU".to_string().into(),
282 CodeRequirementMatchExpression::Equal(team_id.to_string().into()),
283 )),
284 )),
285 )),
286 )
287}
288
289pub fn worldwide_developer_relations_signed_expression(
294 leaf_common_name: impl ToString,
295) -> CodeRequirementExpression<'static> {
296 CodeRequirementExpression::And(
298 Box::new(CodeRequirementExpression::AnchorAppleGeneric),
299 Box::new(CodeRequirementExpression::And(
301 Box::new(CodeRequirementExpression::CertificateField(
302 0,
303 "subject.CN".to_string().into(),
304 CodeRequirementMatchExpression::Equal(leaf_common_name.to_string().into()),
305 )),
306 Box::new(CodeRequirementExpression::CertificateGeneric(
308 1,
309 CertificateAuthorityExtension::AppleWorldwideDeveloperRelations.as_oid(),
310 CodeRequirementMatchExpression::Exists,
311 )),
312 )),
313 )
314}
315
316pub fn non_apple_signed_expression(
321 signing_cert: &CapturedX509Certificate,
322 chain: &[CapturedX509Certificate],
323) -> Result<CodeRequirementExpression<'static>, AppleCodesignError> {
324 let leaf_raw: &x509_certificate::rfc5280::Certificate = signing_cert.as_ref();
325
326 let leaf_organization = leaf_raw
327 .tbs_certificate
328 .subject
329 .iter_organization()
330 .next()
331 .and_then(|o| o.to_string().ok());
332
333 let mut pin_index = 0i32;
337
338 if let Some(leaf_organization) = leaf_organization {
339 for cert in chain.iter() {
340 let ca_raw: &x509_certificate::rfc5280::Certificate = cert.as_ref();
341
342 if let Some(org) = ca_raw
343 .tbs_certificate
344 .subject
345 .iter_organization()
346 .next()
347 .and_then(|o| o.to_string().ok())
348 {
349 if org != leaf_organization {
350 break;
351 }
352
353 pin_index += 1;
354 }
355 }
356 }
357
358 if pin_index as usize == chain.len() {
361 pin_index = -1;
362 }
363
364 let digest = signing_cert
365 .fingerprint(x509_certificate::DigestAlgorithm::Sha1)?
366 .as_ref()
367 .to_vec();
368
369 Ok(CodeRequirementExpression::AnchorCertificateHash(
370 pin_index,
371 digest.into(),
372 ))
373}
374
375#[cfg(test)]
376mod test {
377 use super::*;
378
379 #[test]
380 fn get_policies() {
381 ExecutionPolicy::DeveloperIdSigned.to_bytes().unwrap();
382 ExecutionPolicy::DeveloperIdNotarizedExecutable
383 .to_bytes()
384 .unwrap();
385 ExecutionPolicy::DeveloperIdNotarizedInstaller
386 .to_bytes()
387 .unwrap();
388 }
389
390 const APPLE_SIGNED_CN: &str = "Apple Development: Gregory Szorc (DD5YMVP48D)";
391 const DEVELOPER_ID_TEXT: &str = "(anchor apple generic) and ((certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */) and ((certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */) and (certificate leaf[subject.OU] = \"MK22MZP987\")))";
392 const WWDR_TEXT: &str = "(anchor apple generic) and ((certificate leaf[subject.CN] = \"Apple Development: Gregory Szorc (DD5YMVP48D)\") and (certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */))";
393
394 fn load_unified_pem(pem_data: &[u8]) -> CapturedX509Certificate {
395 pem::parse_many(pem_data)
396 .unwrap()
397 .into_iter()
398 .filter_map(|doc| {
399 if doc.tag() == "CERTIFICATE" {
400 Some(doc.contents().to_vec())
401 } else {
402 None
403 }
404 })
405 .map(|der| CapturedX509Certificate::from_der(der).unwrap())
406 .next()
407 .unwrap()
408 }
409
410 #[test]
411 fn developer_id_requirements_derive() {
412 let der = include_bytes!("testdata/apple-signed-developer-id-application.cer");
413 let cert = CapturedX509Certificate::from_der(der.to_vec()).unwrap();
414
415 assert_eq!(
416 developer_id_signed_expression(cert.apple_team_id().unwrap()).to_string(),
417 DEVELOPER_ID_TEXT
418 );
419 assert_eq!(
420 derive_designated_requirements(&cert, &[], None)
421 .unwrap()
422 .to_string(),
423 DEVELOPER_ID_TEXT
424 );
425 }
426
427 #[test]
428 fn worldwide_developer_relations() {
429 assert_eq!(
430 worldwide_developer_relations_signed_expression(APPLE_SIGNED_CN).to_string(),
431 WWDR_TEXT
432 );
433 }
434
435 #[test]
436 fn non_apple_signed() {
437 let self_signed = load_unified_pem(include_bytes!(
438 "testdata/self-signed-rsa-apple-development.pem"
439 ));
440
441 assert_eq!(
442 non_apple_signed_expression(&self_signed, &[])
443 .unwrap()
444 .to_string(),
445 "certificate root = H\"e1c7216e46533c923b7cfc94e86c7043790b96e9\""
446 );
447
448 let apple_development = CapturedX509Certificate::from_der(
451 include_bytes!("testdata/apple-signed-apple-development.cer").to_vec(),
452 )
453 .unwrap();
454 let chain = apple_development.apple_root_certificate_chain().unwrap();
455
456 assert_eq!(
457 non_apple_signed_expression(&apple_development, &chain[1..])
458 .unwrap()
459 .to_string(),
460 "certificate leaf = H\"5eeadb4befce055e06b4239ad4c5f0d1bfd6af8f\""
461 );
462 }
463
464 #[test]
465 fn apple_signed_auto_derive() {
466 let apple_development = CapturedX509Certificate::from_der(
467 include_bytes!("testdata/apple-signed-apple-development.cer").to_vec(),
468 )
469 .unwrap();
470 let apple_distribution = CapturedX509Certificate::from_der(
471 include_bytes!("testdata/apple-signed-apple-distribution.cer").to_vec(),
472 )
473 .unwrap();
474 let developer_id_application = CapturedX509Certificate::from_der(
475 include_bytes!("testdata/apple-signed-developer-id-application.cer").to_vec(),
476 )
477 .unwrap();
478 let developer_id_installer = CapturedX509Certificate::from_der(
479 include_bytes!("testdata/apple-signed-developer-id-installer.cer").to_vec(),
480 )
481 .unwrap();
482 let mac_installer_distribution = CapturedX509Certificate::from_der(
483 include_bytes!("testdata/apple-signed-3rd-party-mac.cer").to_vec(),
484 )
485 .unwrap();
486
487 assert_eq!(
488 derive_designated_requirements(&apple_development, &[], None)
489 .unwrap()
490 .to_string(),
491 WWDR_TEXT
492 );
493 assert_eq!(
494 derive_designated_requirements(&apple_distribution, &[], None)
495 .unwrap()
496 .to_string(),
497 worldwide_developer_relations_signed_expression(
498 "Apple Distribution: Gregory Szorc (MK22MZP987)"
499 )
500 .to_string()
501 );
502 assert_eq!(
503 derive_designated_requirements(&developer_id_application, &[], None)
504 .unwrap()
505 .to_string(),
506 DEVELOPER_ID_TEXT
507 );
508 assert_eq!(
509 derive_designated_requirements(&developer_id_installer, &[], None)
510 .unwrap()
511 .to_string(),
512 developer_id_signed_expression("MK22MZP987").to_string()
513 );
514 assert_eq!(
515 derive_designated_requirements(&mac_installer_distribution, &[], None)
516 .unwrap()
517 .to_string(),
518 worldwide_developer_relations_signed_expression(
519 "3rd Party Mac Developer Installer: Gregory Szorc (MK22MZP987)"
520 )
521 .to_string()
522 );
523 }
524
525 #[test]
526 fn self_signed_auto_derive() {
527 let apple_development = load_unified_pem(include_bytes!(
528 "testdata/self-signed-rsa-apple-development.pem"
529 ));
530 let apple_distribution = load_unified_pem(include_bytes!(
531 "testdata/self-signed-rsa-apple-distribution.pem"
532 ));
533 let developer_id_application = load_unified_pem(include_bytes!(
534 "testdata/self-signed-rsa-developer-id-application.pem"
535 ));
536 let developer_id_installer = load_unified_pem(include_bytes!(
537 "testdata/self-signed-rsa-developer-id-installer.pem"
538 ));
539 let mac_installer_distribution = load_unified_pem(include_bytes!(
540 "testdata/self-signed-rsa-mac-installer-distribution.pem"
541 ));
542
543 let derive = |cert| -> String {
544 derive_designated_requirements(cert, &[], None)
545 .unwrap()
546 .to_string()
547 };
548
549 assert_eq!(
550 derive(&apple_development),
551 "certificate root = H\"e1c7216e46533c923b7cfc94e86c7043790b96e9\""
552 );
553 assert_eq!(
554 derive(&apple_distribution),
555 "certificate root = H\"0383efdf909250708bf2de4d43753836ccb3d608\""
556 );
557 assert_eq!(
558 derive(&developer_id_application),
559 "certificate root = H\"3acf1d302fe3a4bba06a3c16aadc908045bc9162\""
560 );
561 assert_eq!(
562 derive(&developer_id_installer),
563 "certificate root = H\"5c1314a89e5a486ac7b1da86b38e08777adca4af\""
564 );
565 assert_eq!(
566 derive(&mac_installer_distribution),
567 "certificate root = H\"58e39fe0fca55e7af4ca00027bc7c59e566e960a\""
568 );
569 }
570}