1use {
6 crate::{
7 cli::{CliCommand, Context},
8 code_directory::CodeDirectoryBlob,
9 cryptography::DigestType,
10 embedded_signature::{Blob, CodeSigningSlot, RequirementSetBlob},
11 error::AppleCodesignError,
12 macho::MachFile,
13 },
14 base64::{engine::general_purpose::STANDARD as STANDARD_ENGINE, Engine},
15 clap::{Parser, Subcommand},
16 cryptographic_message_syntax::SignedData,
17 std::{io::Write, path::PathBuf},
18};
19
20fn print_signed_data(
21 prefix: &str,
22 signed_data: &SignedData,
23 external_content: Option<Vec<u8>>,
24) -> Result<(), AppleCodesignError> {
25 println!(
26 "{}signed content (embedded): {:?}",
27 prefix,
28 signed_data.signed_content().map(hex::encode)
29 );
30 println!(
31 "{}signed content (external): {:?}... ({} bytes)",
32 prefix,
33 external_content.as_ref().map(|x| hex::encode(&x[0..40])),
34 external_content.as_ref().map(|x| x.len()).unwrap_or(0),
35 );
36
37 let content = if let Some(v) = signed_data.signed_content() {
38 Some(v)
39 } else {
40 external_content.as_ref().map(|v| v.as_ref())
41 };
42
43 if let Some(content) = content {
44 println!(
45 "{}signed content SHA-1: {}",
46 prefix,
47 hex::encode(DigestType::Sha1.digest_data(content)?)
48 );
49 println!(
50 "{}signed content SHA-256: {}",
51 prefix,
52 hex::encode(DigestType::Sha256.digest_data(content)?)
53 );
54 println!(
55 "{}signed content SHA-384: {}",
56 prefix,
57 hex::encode(DigestType::Sha384.digest_data(content)?)
58 );
59 println!(
60 "{}signed content SHA-512: {}",
61 prefix,
62 hex::encode(DigestType::Sha512.digest_data(content)?)
63 );
64 }
65 println!(
66 "{}certificate count: {}",
67 prefix,
68 signed_data.certificates().count()
69 );
70 for (i, cert) in signed_data.certificates().enumerate() {
71 println!(
72 "{}certificate #{}: subject CN={}; self signed={}",
73 prefix,
74 i,
75 cert.subject_common_name()
76 .unwrap_or_else(|| "<unknown>".to_string()),
77 cert.subject_is_issuer()
78 );
79 }
80 println!("{}signer count: {}", prefix, signed_data.signers().count());
81 for (i, signer) in signed_data.signers().enumerate() {
82 println!(
83 "{}signer #{}: digest algorithm: {:?}",
84 prefix,
85 i,
86 signer.digest_algorithm()
87 );
88 println!(
89 "{}signer #{}: signature algorithm: {:?}",
90 prefix,
91 i,
92 signer.signature_algorithm()
93 );
94
95 if let Some(sa) = signer.signed_attributes() {
96 println!(
97 "{}signer #{}: content type: {}",
98 prefix,
99 i,
100 sa.content_type()
101 );
102 println!(
103 "{}signer #{}: message digest: {}",
104 prefix,
105 i,
106 hex::encode(sa.message_digest())
107 );
108 println!(
109 "{}signer #{}: signing time: {:?}",
110 prefix,
111 i,
112 sa.signing_time()
113 );
114 }
115
116 let digested_data = signer.signed_content_with_signed_data(signed_data);
117
118 println!(
119 "{}signer #{}: signature content SHA-1: {}",
120 prefix,
121 i,
122 hex::encode(DigestType::Sha1.digest_data(&digested_data)?)
123 );
124 println!(
125 "{}signer #{}: signature content SHA-256: {}",
126 prefix,
127 i,
128 hex::encode(DigestType::Sha256.digest_data(&digested_data)?)
129 );
130 println!(
131 "{}signer #{}: signature content SHA-384: {}",
132 prefix,
133 i,
134 hex::encode(DigestType::Sha384.digest_data(&digested_data)?)
135 );
136 println!(
137 "{}signer #{}: signature content SHA-512: {}",
138 prefix,
139 i,
140 hex::encode(DigestType::Sha512.digest_data(&digested_data)?)
141 );
142
143 if signed_data.signed_content().is_some() {
144 println!(
145 "{}signer #{}: digest valid: {}",
146 prefix,
147 i,
148 signer
149 .verify_message_digest_with_signed_data(signed_data)
150 .is_ok()
151 );
152 }
153 println!(
154 "{}signer #{}: signature valid: {}",
155 prefix,
156 i,
157 signer
158 .verify_signature_with_signed_data(signed_data)
159 .is_ok()
160 );
161
162 println!(
163 "{}signer #{}: time-stamp token present: {}",
164 prefix,
165 i,
166 signer.time_stamp_token_signed_data()?.is_some()
167 );
168
169 if let Some(tsp_signed_data) = signer.time_stamp_token_signed_data()? {
170 let prefix = format!("{prefix}signer #{i}: time-stamp token: ");
171
172 print_signed_data(&prefix, &tsp_signed_data, None)?;
173 }
174 }
175
176 Ok(())
177}
178
179#[derive(Clone, Parser)]
180struct ExtractCommon {
181 path: PathBuf,
183}
184
185#[derive(Clone, Subcommand)]
186enum ExtractData {
187 Blobs(ExtractCommon),
189 CmsInfo(ExtractCommon),
191 CmsPem(ExtractCommon),
193 CmsRaw(ExtractCommon),
195 Cms(ExtractCommon),
197 CodeDirectory(ExtractCommon),
199 CodeDirectoryRaw(ExtractCommon),
201 CodeDirectorySerialized(ExtractCommon),
203 CodeDirectorySerializedRaw(ExtractCommon),
207 LinkeditInfo(ExtractCommon),
209 LinkeditSegmentRaw(ExtractCommon),
211 MachoHeader(ExtractCommon),
213 MachoLoadCommands(ExtractCommon),
215 MachoLoadCommandsRaw(ExtractCommon),
217 MachoSegments(ExtractCommon),
219 MachoTarget(ExtractCommon),
221 Requirements(ExtractCommon),
223 RequirementsRaw(ExtractCommon),
225 RequirementsRust(ExtractCommon),
227 RequirementsSerialized(ExtractCommon),
229 RequirementsSerializedRaw(ExtractCommon),
231 SignatureRaw(ExtractCommon),
233 Superblob(ExtractCommon),
235}
236
237impl ExtractData {
238 fn common_args(&self) -> &ExtractCommon {
239 match self {
240 ExtractData::Blobs(x) => x,
241 ExtractData::CmsInfo(x) => x,
242 ExtractData::CmsPem(x) => x,
243 ExtractData::CmsRaw(x) => x,
244 ExtractData::Cms(x) => x,
245 ExtractData::CodeDirectoryRaw(x) => x,
246 ExtractData::CodeDirectorySerializedRaw(x) => x,
247 ExtractData::CodeDirectorySerialized(x) => x,
248 ExtractData::CodeDirectory(x) => x,
249 ExtractData::LinkeditInfo(x) => x,
250 ExtractData::LinkeditSegmentRaw(x) => x,
251 ExtractData::MachoHeader(x) => x,
252 ExtractData::MachoLoadCommands(x) => x,
253 ExtractData::MachoLoadCommandsRaw(x) => x,
254 ExtractData::MachoSegments(x) => x,
255 ExtractData::MachoTarget(x) => x,
256 ExtractData::RequirementsRaw(x) => x,
257 ExtractData::RequirementsRust(x) => x,
258 ExtractData::RequirementsSerializedRaw(x) => x,
259 ExtractData::RequirementsSerialized(x) => x,
260 ExtractData::Requirements(x) => x,
261 ExtractData::SignatureRaw(x) => x,
262 ExtractData::Superblob(x) => x,
263 }
264 }
265}
266
267#[derive(Parser)]
268pub struct Extract {
269 #[arg(long, global = true, default_value = "0")]
271 universal_index: usize,
272
273 #[command(subcommand)]
275 data: ExtractData,
276}
277
278impl CliCommand for Extract {
279 fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> {
280 let common = self.data.common_args();
281
282 let data = std::fs::read(&common.path)?;
283 let mach = MachFile::parse(&data)?;
284 let macho = mach.nth_macho(self.universal_index)?;
285
286 match self.data {
287 ExtractData::Blobs(_) => {
288 let embedded = macho
289 .code_signature()?
290 .ok_or(AppleCodesignError::BinaryNoCodeSignature)?;
291
292 for blob in embedded.blobs {
293 let parsed = blob.into_parsed_blob()?;
294 println!("{parsed:#?}");
295 }
296 }
297 ExtractData::CmsInfo(_) => {
298 let embedded = macho
299 .code_signature()?
300 .ok_or(AppleCodesignError::BinaryNoCodeSignature)?;
301
302 if let Some(cms) = embedded.signature_data()? {
303 let signed_data = SignedData::parse_ber(cms)?;
304
305 let cd_data = if let Ok(Some(blob)) = embedded.code_directory() {
306 Some(blob.to_blob_bytes()?)
307 } else {
308 None
309 };
310
311 print_signed_data("", &signed_data, cd_data)?;
312 } else {
313 eprintln!("no CMS data");
314 }
315 }
316 ExtractData::CmsPem(_) => {
317 let embedded = macho
318 .code_signature()?
319 .ok_or(AppleCodesignError::BinaryNoCodeSignature)?;
320
321 if let Some(cms) = embedded.signature_data()? {
322 print!("{}", pem::encode(&pem::Pem::new("PKCS7", cms.to_vec())));
323 } else {
324 eprintln!("no CMS data");
325 }
326 }
327 ExtractData::CmsRaw(_) => {
328 let embedded = macho
329 .code_signature()?
330 .ok_or(AppleCodesignError::BinaryNoCodeSignature)?;
331
332 if let Some(cms) = embedded.signature_data()? {
333 std::io::stdout().write_all(cms)?;
334 } else {
335 eprintln!("no CMS data");
336 }
337 }
338 ExtractData::Cms(_) => {
339 let embedded = macho
340 .code_signature()?
341 .ok_or(AppleCodesignError::BinaryNoCodeSignature)?;
342
343 if let Some(signed_data) = embedded.signed_data()? {
344 println!("{signed_data:#?}");
345 } else {
346 eprintln!("no CMS data");
347 }
348 }
349 ExtractData::CodeDirectoryRaw(_) => {
350 let embedded = macho
351 .code_signature()?
352 .ok_or(AppleCodesignError::BinaryNoCodeSignature)?;
353
354 if let Some(blob) = embedded.find_slot(CodeSigningSlot::CodeDirectory) {
355 std::io::stdout().write_all(blob.data)?;
356 } else {
357 eprintln!("no code directory");
358 }
359 }
360 ExtractData::CodeDirectorySerializedRaw(_) => {
361 let embedded = macho
362 .code_signature()?
363 .ok_or(AppleCodesignError::BinaryNoCodeSignature)?;
364
365 if let Ok(Some(cd)) = embedded.code_directory() {
366 std::io::stdout().write_all(&cd.to_blob_bytes()?)?;
367 } else {
368 eprintln!("no code directory");
369 }
370 }
371 ExtractData::CodeDirectorySerialized(_) => {
372 let embedded = macho
373 .code_signature()?
374 .ok_or(AppleCodesignError::BinaryNoCodeSignature)?;
375
376 if let Ok(Some(cd)) = embedded.code_directory() {
377 let serialized = cd.to_blob_bytes()?;
378 println!("{:#?}", CodeDirectoryBlob::from_blob_bytes(&serialized)?);
379 }
380 }
381 ExtractData::CodeDirectory(_) => {
382 let embedded = macho
383 .code_signature()?
384 .ok_or(AppleCodesignError::BinaryNoCodeSignature)?;
385
386 if let Some(cd) = embedded.code_directory()? {
387 println!("{cd:#?}");
388 } else {
389 eprintln!("no code directory");
390 }
391 }
392 ExtractData::LinkeditInfo(_) => {
393 let sig = macho
394 .find_signature_data()?
395 .ok_or(AppleCodesignError::BinaryNoCodeSignature)?;
396 println!("__LINKEDIT segment index: {}", sig.linkedit_segment_index);
397 println!(
398 "__LINKEDIT segment start offset: {}",
399 sig.linkedit_segment_start_offset
400 );
401 println!(
402 "__LINKEDIT segment end offset: {}",
403 sig.linkedit_segment_end_offset
404 );
405 println!(
406 "__LINKEDIT segment size: {}",
407 sig.linkedit_segment_data.len()
408 );
409 println!(
410 "__LINKEDIT signature global start offset: {}",
411 sig.signature_file_start_offset
412 );
413 println!(
414 "__LINKEDIT signature global end offset: {}",
415 sig.signature_file_end_offset
416 );
417 println!(
418 "__LINKEDIT signature local segment start offset: {}",
419 sig.signature_segment_start_offset
420 );
421 println!(
422 "__LINKEDIT signature local segment end offset: {}",
423 sig.signature_segment_end_offset
424 );
425 println!("__LINKEDIT signature size: {}", sig.signature_data.len());
426 }
427 ExtractData::LinkeditSegmentRaw(_) => {
428 let sig = macho
429 .find_signature_data()?
430 .ok_or(AppleCodesignError::BinaryNoCodeSignature)?;
431 std::io::stdout().write_all(sig.linkedit_segment_data)?;
432 }
433 ExtractData::MachoHeader(_) => {
434 println!("{:#?}", macho.macho.header);
435 }
436 ExtractData::MachoLoadCommands(_) => {
437 println!("load command count: {}", macho.macho.load_commands.len());
438
439 for command in &macho.macho.load_commands {
440 println!(
441 "{}; offsets=0x{:x}-0x{:x} ({}-{}); size={}",
442 goblin::mach::load_command::cmd_to_str(command.command.cmd()),
443 command.offset,
444 command.offset + command.command.cmdsize(),
445 command.offset,
446 command.offset + command.command.cmdsize(),
447 command.command.cmdsize(),
448 );
449 }
450 }
451 ExtractData::MachoLoadCommandsRaw(_) => {
452 for command in &macho.macho.load_commands {
453 println!("{:?}", command);
454 }
455 }
456 ExtractData::MachoSegments(_) => {
457 println!("segments count: {}", macho.macho.segments.len());
458 for (segment_index, segment) in macho.macho.segments.iter().enumerate() {
459 let sections = segment.sections()?;
460
461 println!(
462 "segment #{}; {}; offsets=0x{:x}-0x{:x} ({}-{}); addresses=0x{:x}-0x{:x}; vm/file size {}/{}; section count {}",
463 segment_index,
464 segment.name()?,
465 segment.fileoff,
466 segment.fileoff as usize + segment.data.len(),
467 segment.fileoff,
468 segment.fileoff as usize + segment.data.len(),
469 segment.vmaddr,
470 segment.vmaddr + segment.vmsize,
471 segment.vmsize,
472 segment.filesize,
473 sections.len()
474 );
475 for (section_index, (section, _)) in sections.into_iter().enumerate() {
476 println!(
477 "segment #{}; section #{}: {}; offsets=0x{:x}-0x{:x} ({}-{}); addresses=0x{:x}-0x{:x}; size {}; align={}; flags={}",
478 segment_index,
479 section_index,
480 section.name()?,
481 section.offset,
482 section.offset as u64 + section.size,
483 section.offset,
484 section.offset as u64 + section.size,
485 section.addr,
486 section.addr + section.size,
487 section.size,
488 section.align,
489 section.flags,
490 );
491 }
492 }
493 }
494 ExtractData::MachoTarget(_) => {
495 if let Some(target) = macho.find_targeting()? {
496 println!("Platform: {}", target.platform);
497 println!("Minimum OS: {}", target.minimum_os_version);
498 println!("SDK: {}", target.sdk_version);
499 } else {
500 println!("Unable to resolve Mach-O targeting from load commands");
501 }
502 }
503 ExtractData::RequirementsRaw(_) => {
504 let embedded = macho
505 .code_signature()?
506 .ok_or(AppleCodesignError::BinaryNoCodeSignature)?;
507
508 if let Some(blob) = embedded.find_slot(CodeSigningSlot::RequirementSet) {
509 std::io::stdout().write_all(blob.data)?;
510 } else {
511 eprintln!("no requirements");
512 }
513 }
514 ExtractData::RequirementsRust(_) => {
515 let embedded = macho
516 .code_signature()?
517 .ok_or(AppleCodesignError::BinaryNoCodeSignature)?;
518
519 if let Some(reqs) = embedded.code_requirements()? {
520 for (typ, req) in &reqs.requirements {
521 for expr in req.parse_expressions()?.iter() {
522 println!("{typ} => {expr:#?}");
523 }
524 }
525 } else {
526 eprintln!("no requirements");
527 }
528 }
529 ExtractData::RequirementsSerializedRaw(_) => {
530 let embedded = macho
531 .code_signature()?
532 .ok_or(AppleCodesignError::BinaryNoCodeSignature)?;
533
534 if let Some(reqs) = embedded.code_requirements()? {
535 std::io::stdout().write_all(&reqs.to_blob_bytes()?)?;
536 } else {
537 eprintln!("no requirements");
538 }
539 }
540 ExtractData::RequirementsSerialized(_) => {
541 let embedded = macho
542 .code_signature()?
543 .ok_or(AppleCodesignError::BinaryNoCodeSignature)?;
544
545 if let Some(reqs) = embedded.code_requirements()? {
546 let serialized = reqs.to_blob_bytes()?;
547 println!("{:#?}", RequirementSetBlob::from_blob_bytes(&serialized)?);
548 } else {
549 eprintln!("no requirements");
550 }
551 }
552 ExtractData::Requirements(_) => {
553 let embedded = macho
554 .code_signature()?
555 .ok_or(AppleCodesignError::BinaryNoCodeSignature)?;
556
557 if let Some(reqs) = embedded.code_requirements()? {
558 for (typ, req) in &reqs.requirements {
559 for expr in req.parse_expressions()?.iter() {
560 println!("{typ} => {expr}");
561 }
562 }
563 } else {
564 eprintln!("no requirements");
565 }
566 }
567 ExtractData::SignatureRaw(_) => {
568 let sig = macho
569 .find_signature_data()?
570 .ok_or(AppleCodesignError::BinaryNoCodeSignature)?;
571 std::io::stdout().write_all(sig.signature_data)?;
572 }
573 ExtractData::Superblob(_) => {
574 let sig = macho
575 .find_signature_data()?
576 .ok_or(AppleCodesignError::BinaryNoCodeSignature)?;
577 let embedded = macho
578 .code_signature()?
579 .ok_or(AppleCodesignError::BinaryNoCodeSignature)?;
580
581 println!("file start offset: {}", sig.signature_file_start_offset);
582 println!("file end offset: {}", sig.signature_file_end_offset);
583 println!(
584 "__LINKEDIT start offset: {}",
585 sig.signature_segment_start_offset
586 );
587 println!(
588 "__LINKEDIT end offset: {}",
589 sig.signature_segment_end_offset
590 );
591 println!("length: {}", embedded.length);
592 println!("blob count: {}", embedded.count);
593 println!("blobs:");
594 for blob in embedded.blobs {
595 println!("- index: {}", blob.index);
596 println!(
597 " offsets: 0x{:x}-0x{:x} ({}-{})",
598 blob.offset,
599 blob.offset + blob.length - 1,
600 blob.offset,
601 blob.offset + blob.length - 1
602 );
603 println!(" length: {}", blob.length);
604 println!(" slot: {:?}", blob.slot);
605 println!(" magic: {:?} (0x{:x})", blob.magic, u32::from(blob.magic));
606 println!(
607 " sha1: {}",
608 hex::encode(blob.digest_with(DigestType::Sha1)?)
609 );
610 println!(
611 " sha256: {}",
612 hex::encode(blob.digest_with(DigestType::Sha256)?)
613 );
614 println!(
615 " sha256-truncated: {}",
616 hex::encode(blob.digest_with(DigestType::Sha256Truncated)?)
617 );
618 println!(
619 " sha384: {}",
620 hex::encode(blob.digest_with(DigestType::Sha384)?),
621 );
622 println!(
623 " sha512: {}",
624 hex::encode(blob.digest_with(DigestType::Sha512)?),
625 );
626 println!(
627 " sha1-base64: {}",
628 STANDARD_ENGINE.encode(blob.digest_with(DigestType::Sha1)?)
629 );
630 println!(
631 " sha256-base64: {}",
632 STANDARD_ENGINE.encode(blob.digest_with(DigestType::Sha256)?)
633 );
634 println!(
635 " sha256-truncated-base64: {}",
636 STANDARD_ENGINE.encode(blob.digest_with(DigestType::Sha256Truncated)?)
637 );
638 println!(
639 " sha384-base64: {}",
640 STANDARD_ENGINE.encode(blob.digest_with(DigestType::Sha384)?)
641 );
642 println!(
643 " sha512-base64: {}",
644 STANDARD_ENGINE.encode(blob.digest_with(DigestType::Sha512)?)
645 );
646 }
647 }
648 }
649
650 Ok(())
651 }
652}