1use {
47 crate::{
48 code_directory::{CodeDirectoryBlob, CodeSignatureFlags},
49 cryptography::{Digest, DigestType},
50 embedded_signature::{BlobData, CodeSigningSlot, EmbeddedSignature, RequirementSetBlob},
51 embedded_signature_builder::EmbeddedSignatureBuilder,
52 AppleCodesignError, SettingsScope, SigningSettings,
53 },
54 log::warn,
55 scroll::{Pread, Pwrite, SizeWith},
56 std::{
57 borrow::Cow,
58 fs::File,
59 io::{Read, Seek, SeekFrom, Write},
60 path::Path,
61 },
62};
63
64const KOLY_SIZE: i64 = 512;
65
66#[derive(Clone, Debug, Eq, Pread, PartialEq, Pwrite, SizeWith)]
70pub struct KolyTrailer {
71 pub signature: [u8; 4],
73 pub version: u32,
74 pub header_size: u32,
75 pub flags: u32,
76 pub running_data_fork_offset: u64,
77 pub data_fork_offset: u64,
78 pub data_fork_length: u64,
79 pub rsrc_fork_offset: u64,
80 pub rsrc_fork_length: u64,
81 pub segment_number: u32,
82 pub segment_count: u32,
83 pub segment_id: [u32; 4],
84 pub data_fork_digest_type: u32,
85 pub data_fork_digest_size: u32,
86 pub data_fork_digest: [u32; 32],
87 pub plist_offset: u64,
88 pub plist_length: u64,
89 pub reserved1: [u64; 8],
90 pub code_signature_offset: u64,
91 pub code_signature_size: u64,
92 pub reserved2: [u64; 5],
93 pub main_digest_type: u32,
94 pub main_digest_size: u32,
95 pub main_digest: [u32; 32],
96 pub image_variant: u32,
97 pub sector_count: u64,
98}
99
100impl KolyTrailer {
101 pub fn read_from<R: Read + Seek>(reader: &mut R) -> Result<Self, AppleCodesignError> {
105 reader.seek(SeekFrom::End(-KOLY_SIZE))?;
106
107 let mut data = vec![];
109 reader.read_to_end(&mut data)?;
110
111 let koly = data.pread_with::<KolyTrailer>(0, scroll::BE)?;
112
113 if &koly.signature != b"koly" {
114 return Err(AppleCodesignError::DmgBadMagic);
115 }
116
117 Ok(koly)
118 }
119
120 pub fn offset_after_plist(&self) -> u64 {
125 self.plist_offset + self.plist_length
126 }
127
128 pub fn digest_for_code_directory(
133 &self,
134 digest: DigestType,
135 ) -> Result<Vec<u8>, AppleCodesignError> {
136 let mut koly = self.clone();
137 koly.code_signature_size = 0;
138 koly.code_signature_offset = self.offset_after_plist();
139
140 let mut buf = [0u8; KOLY_SIZE as usize];
141 buf.pwrite_with(koly, 0, scroll::BE)?;
142
143 digest.digest_data(&buf)
144 }
145}
146
147pub struct DmgReader {
151 koly: KolyTrailer,
152
153 code_signature_data: Option<Vec<u8>>,
155}
156
157impl DmgReader {
158 pub fn new<R: Read + Seek>(reader: &mut R) -> Result<Self, AppleCodesignError> {
160 let koly = KolyTrailer::read_from(reader)?;
161
162 let code_signature_offset = koly.code_signature_offset;
163 let code_signature_size = koly.code_signature_size;
164
165 let code_signature_data = if code_signature_offset != 0 && code_signature_size != 0 {
166 reader.seek(SeekFrom::Start(code_signature_offset))?;
167 let mut data = vec![];
168 reader.take(code_signature_size).read_to_end(&mut data)?;
169
170 Some(data)
171 } else {
172 None
173 };
174
175 Ok(Self {
176 koly,
177 code_signature_data,
178 })
179 }
180
181 pub fn koly(&self) -> &KolyTrailer {
183 &self.koly
184 }
185
186 pub fn embedded_signature(&self) -> Result<Option<EmbeddedSignature<'_>>, AppleCodesignError> {
188 if let Some(data) = &self.code_signature_data {
189 Ok(Some(EmbeddedSignature::from_bytes(data)?))
190 } else {
191 Ok(None)
192 }
193 }
194
195 fn digest_slice_with<R: Read + Seek>(
197 &self,
198 digest: DigestType,
199 reader: &mut R,
200 offset: u64,
201 length: u64,
202 ) -> Result<Digest<'static>, AppleCodesignError> {
203 reader.seek(SeekFrom::Start(offset))?;
204
205 let mut reader = reader.take(length);
206
207 let mut d = digest.as_hasher()?;
208
209 loop {
210 let mut buffer = [0u8; 16384];
211 let count = reader.read(&mut buffer)?;
212
213 d.update(&buffer[0..count]);
214
215 if count == 0 {
216 break;
217 }
218 }
219
220 Ok(Digest {
221 data: d.finish().as_ref().to_vec().into(),
222 })
223 }
224
225 pub fn digest_content_with<R: Read + Seek>(
229 &self,
230 digest: DigestType,
231 reader: &mut R,
232 ) -> Result<Digest<'static>, AppleCodesignError> {
233 if self.koly.code_signature_offset != 0 {
234 self.digest_slice_with(digest, reader, 0, self.koly.code_signature_offset)
235 } else {
236 reader.seek(SeekFrom::End(-KOLY_SIZE))?;
237 let size = reader.stream_position()?;
238
239 self.digest_slice_with(digest, reader, 0, size)
240 }
241 }
242}
243
244pub fn path_is_dmg(path: impl AsRef<Path>) -> Result<bool, AppleCodesignError> {
248 let mut fh = File::open(path.as_ref())?;
249
250 Ok(KolyTrailer::read_from(&mut fh).is_ok())
251}
252
253#[derive(Clone, Debug, Default)]
255pub struct DmgSigner {}
256
257impl DmgSigner {
258 pub fn sign_file(
265 &self,
266 settings: &SigningSettings,
267 fh: &mut File,
268 ) -> Result<(), AppleCodesignError> {
269 warn!("signing DMG");
270
271 let koly = DmgReader::new(fh)?.koly().clone();
272 let signature = self.create_superblob(settings, fh)?;
273
274 Self::write_embedded_signature(fh, koly, &signature)
275 }
276
277 pub fn staple_file(
279 &self,
280 fh: &mut File,
281 ticket_data: Vec<u8>,
282 ) -> Result<(), AppleCodesignError> {
283 warn!(
284 "stapling DMG with {} byte notarization ticket",
285 ticket_data.len()
286 );
287
288 let reader = DmgReader::new(fh)?;
289 let koly = reader.koly().clone();
290 let signature = reader
291 .embedded_signature()?
292 .ok_or(AppleCodesignError::DmgStapleNoSignature)?;
293
294 let mut builder = EmbeddedSignatureBuilder::new_for_stapling(signature)?;
295 builder.add_notarization_ticket(ticket_data)?;
296
297 let signature = builder.create_superblob()?;
298
299 Self::write_embedded_signature(fh, koly, &signature)
300 }
301
302 fn write_embedded_signature(
303 fh: &mut File,
304 mut koly: KolyTrailer,
305 signature: &[u8],
306 ) -> Result<(), AppleCodesignError> {
307 warn!("writing {} byte signature", signature.len());
308 fh.seek(SeekFrom::Start(koly.offset_after_plist()))?;
309 fh.write_all(signature)?;
310
311 koly.code_signature_offset = koly.offset_after_plist();
312 koly.code_signature_size = signature.len() as _;
313
314 let mut trailer = [0u8; KOLY_SIZE as usize];
315 trailer.pwrite_with(&koly, 0, scroll::BE)?;
316
317 fh.write_all(&trailer)?;
318
319 fh.set_len(koly.code_signature_offset + koly.code_signature_size + KOLY_SIZE as u64)?;
320
321 Ok(())
322 }
323
324 pub fn create_superblob<F: Read + Write + Seek>(
326 &self,
327 settings: &SigningSettings,
328 fh: &mut F,
329 ) -> Result<Vec<u8>, AppleCodesignError> {
330 let mut builder = EmbeddedSignatureBuilder::default();
331
332 for (slot, blob) in self.create_special_blobs()? {
333 builder.add_blob(slot, blob)?;
334 }
335
336 builder.add_code_directory(
337 CodeSigningSlot::CodeDirectory,
338 self.create_code_directory(settings, fh)?,
339 )?;
340
341 if let Some((signing_key, signing_cert)) = settings.signing_key() {
342 builder.create_cms_signature(
343 signing_key,
344 signing_cert,
345 settings.time_stamp_url(),
346 settings.certificate_chain().iter().cloned(),
347 settings.signing_time(),
348 )?;
349 }
350
351 builder.create_superblob()
352 }
353
354 pub fn create_code_directory<F: Read + Write + Seek>(
359 &self,
360 settings: &SigningSettings,
361 fh: &mut F,
362 ) -> Result<CodeDirectoryBlob<'static>, AppleCodesignError> {
363 let reader = DmgReader::new(fh)?;
364
365 let mut flags = settings
366 .code_signature_flags(SettingsScope::Main)
367 .unwrap_or_else(CodeSignatureFlags::empty);
368
369 if settings.signing_key().is_some() {
370 flags -= CodeSignatureFlags::ADHOC;
371 } else {
372 flags |= CodeSignatureFlags::ADHOC;
373 }
374
375 warn!("using code signature flags: {:?}", flags);
376
377 let ident = Cow::Owned(
378 settings
379 .binary_identifier(SettingsScope::Main)
380 .ok_or(AppleCodesignError::NoIdentifier)?
381 .to_string(),
382 );
383
384 warn!("using identifier {}", ident);
385
386 let digest_type = settings.digest_type(SettingsScope::Main);
387
388 let code_hashes = vec![reader.digest_content_with(digest_type, fh)?];
389
390 let koly_digest = reader.koly().digest_for_code_directory(digest_type)?;
391
392 let mut cd = CodeDirectoryBlob {
393 version: 0x20100,
394 flags,
395 code_limit: reader.koly().offset_after_plist() as u32,
396 digest_size: digest_type.hash_len()? as u8,
397 digest_type,
398 page_size: 1,
399 ident,
400 code_digests: code_hashes,
401 ..Default::default()
402 };
403
404 cd.set_slot_digest(CodeSigningSlot::RepSpecific, koly_digest)?;
405
406 Ok(cd)
407 }
408
409 pub fn create_special_blobs(
411 &self,
412 ) -> Result<Vec<(CodeSigningSlot, BlobData)>, AppleCodesignError> {
413 Ok(vec![(
414 CodeSigningSlot::RequirementSet,
415 RequirementSetBlob::default().into(),
416 )])
417 }
418}