isideload_apple_codesign/
embedded_signature_builder.rs1use {
8 crate::{
9 code_directory::CodeDirectoryBlob,
10 embedded_signature::{
11 create_superblob, Blob, BlobData, BlobWrapperBlob, CodeSigningMagic, CodeSigningSlot,
12 EmbeddedSignature,
13 },
14 error::AppleCodesignError,
15 },
16 bcder::{encode::PrimitiveContent, Oid},
17 bytes::Bytes,
18 cryptographic_message_syntax::{asn1::rfc5652::OID_ID_DATA, SignedDataBuilder, SignerBuilder},
19 log::{info, warn},
20 reqwest::Url,
21 std::collections::BTreeMap,
22 x509_certificate::{
23 rfc5652::AttributeValue, CapturedX509Certificate, DigestAlgorithm, KeyInfoSigner,
24 },
25};
26
27pub const CD_DIGESTS_PLIST_OID: bcder::ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 9, 1]);
31
32pub const CD_DIGESTS_OID: bcder::ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 9, 2]);
36
37#[derive(Clone, Copy, Debug, PartialEq)]
38#[derive(Default)]
39enum BlobsState {
40 #[default]
41 Empty,
42 SpecialAdded,
43 CodeDirectoryAdded,
44 SignatureAdded,
45 TicketAdded,
46}
47
48
49#[derive(Debug, Default)]
53pub struct EmbeddedSignatureBuilder<'a> {
54 state: BlobsState,
55 blobs: BTreeMap<CodeSigningSlot, BlobData<'a>>,
56}
57
58impl<'a> EmbeddedSignatureBuilder<'a> {
59 pub fn new_for_stapling(signature: EmbeddedSignature<'a>) -> Result<Self, AppleCodesignError> {
65 let blobs = signature
66 .blobs
67 .into_iter()
68 .map(|blob| {
69 let parsed = blob.into_parsed_blob()?;
70
71 Ok((parsed.blob_entry.slot, parsed.blob))
72 })
73 .collect::<Result<BTreeMap<_, _>, AppleCodesignError>>()?;
74
75 Ok(Self {
76 state: BlobsState::CodeDirectoryAdded,
77 blobs,
78 })
79 }
80
81 pub fn code_directory(&self) -> Option<&CodeDirectoryBlob<'_>> {
83 self.blobs.get(&CodeSigningSlot::CodeDirectory).map(|blob| {
84 if let BlobData::CodeDirectory(cd) = blob {
85 (*cd).as_ref()
86 } else {
87 panic!("a non code directory should never be stored in the code directory slot");
88 }
89 })
90 }
91
92 pub fn add_blob(
101 &mut self,
102 slot: CodeSigningSlot,
103 blob: BlobData<'a>,
104 ) -> Result<(), AppleCodesignError> {
105 match self.state {
106 BlobsState::Empty | BlobsState::SpecialAdded => {}
107 BlobsState::CodeDirectoryAdded
108 | BlobsState::SignatureAdded
109 | BlobsState::TicketAdded => {
110 return Err(AppleCodesignError::SignatureBuilder(
111 "cannot add blobs after code directory or signature is registered",
112 ));
113 }
114 }
115
116 if matches!(
117 blob,
118 BlobData::CodeDirectory(_)
119 | BlobData::EmbeddedSignature(_)
120 | BlobData::EmbeddedSignatureOld(_)
121 ) {
122 return Err(AppleCodesignError::SignatureBuilder(
123 "cannot register code directory or signature blob via add_blob()",
124 ));
125 }
126
127 self.blobs.insert(slot, blob);
128
129 self.state = BlobsState::SpecialAdded;
130
131 Ok(())
132 }
133
134 pub fn add_code_directory(
146 &mut self,
147 cd_slot: CodeSigningSlot,
148 mut cd: CodeDirectoryBlob<'a>,
149 ) -> Result<&CodeDirectoryBlob<'_>, AppleCodesignError> {
150 if matches!(self.state, BlobsState::SignatureAdded) {
151 return Err(AppleCodesignError::SignatureBuilder(
152 "cannot add code directory after signature data added",
153 ));
154 }
155
156 for (slot, blob) in &self.blobs {
157 if !slot.is_code_directory_specials_expressible() {
159 continue;
160 }
161
162 let digest = blob.digest_with(cd.digest_type)?;
163
164 cd.set_slot_digest(*slot, digest)?;
165 }
166
167 self.blobs.insert(cd_slot, cd.into());
168 self.state = BlobsState::CodeDirectoryAdded;
169
170 Ok(self.code_directory().expect("we just inserted this key"))
171 }
172
173 pub fn add_alternative_code_directory(
178 &mut self,
179 cd: CodeDirectoryBlob<'a>,
180 ) -> Result<&CodeDirectoryBlob<'_>, AppleCodesignError> {
181 let mut our_slot = CodeSigningSlot::AlternateCodeDirectory0;
182
183 for slot in self.blobs.keys() {
184 if slot.is_alternative_code_directory() {
185 our_slot = CodeSigningSlot::from(u32::from(*slot) + 1);
186
187 if !our_slot.is_alternative_code_directory() {
188 return Err(AppleCodesignError::SignatureBuilder(
189 "no more available alternative code directory slots",
190 ));
191 }
192 }
193 }
194
195 self.add_code_directory(our_slot, cd)
196 }
197
198 pub fn create_cms_signature(
213 &mut self,
214 signing_key: &dyn KeyInfoSigner,
215 signing_cert: &CapturedX509Certificate,
216 time_stamp_url: Option<&Url>,
217 certificates: impl Iterator<Item = CapturedX509Certificate>,
218 signing_time: Option<chrono::DateTime<chrono::Utc>>,
219 ) -> Result<(), AppleCodesignError> {
220 let main_cd = self
221 .code_directory()
222 .ok_or(AppleCodesignError::SignatureBuilder(
223 "cannot create CMS signature unless code directory is present",
224 ))?;
225
226 if let Some(cn) = signing_cert.subject_common_name() {
227 warn!("creating cryptographic signature with certificate {}", cn);
228 }
229
230 let mut cdhashes = vec![];
231 let mut attributes = vec![];
232
233 for (slot, blob) in &self.blobs {
234 if *slot == CodeSigningSlot::CodeDirectory || slot.is_alternative_code_directory() {
235 if let BlobData::CodeDirectory(cd) = blob {
236 let mut digest = cd.digest_with(cd.digest_type)?;
239 digest.truncate(20);
240 cdhashes.push(plist::Value::Data(digest));
241
242 let digest = cd.digest_with(cd.digest_type)?;
245 let alg = DigestAlgorithm::try_from(cd.digest_type)?;
246
247 attributes.push(AttributeValue::new(bcder::Captured::from_values(
248 bcder::Mode::Der,
249 bcder::encode::sequence((
250 Oid::from(alg).encode_ref(),
251 bcder::OctetString::new(digest.into()).encode_ref(),
252 )),
253 )));
254 } else {
255 return Err(AppleCodesignError::SignatureBuilder(
256 "unexpected blob type in code directory slot",
257 ));
258 }
259 }
260 }
261
262 let mut plist_dict = plist::Dictionary::new();
263 plist_dict.insert("cdhashes".to_string(), plist::Value::Array(cdhashes));
264
265 let mut plist_xml = vec![];
266 plist::Value::from(plist_dict)
267 .to_writer_xml(&mut plist_xml)
268 .map_err(AppleCodesignError::CodeDirectoryPlist)?;
269 plist_xml.push(b'\n');
272
273 let signer = SignerBuilder::new(signing_key, signing_cert.clone())
274 .message_id_content(main_cd.to_blob_bytes()?)
275 .signed_attribute_octet_string(
276 Oid(Bytes::copy_from_slice(CD_DIGESTS_PLIST_OID.as_ref())),
277 &plist_xml,
278 );
279
280 let signer = signer.signed_attribute(Oid(CD_DIGESTS_OID.as_ref().into()), attributes);
281
282 let signer = if let Some(time_stamp_url) = time_stamp_url {
283 info!("Using time-stamp server {}", time_stamp_url);
284 signer.time_stamp_url(time_stamp_url.clone())?
285 } else {
286 signer
287 };
288
289 let builder = SignedDataBuilder::default()
290 .content_type(Oid(OID_ID_DATA.as_ref().into()))
294 .signer(signer)
295 .certificates(certificates);
296
297 let builder = if let Some(time) = signing_time {
298 info!("Using signing time {}", time.to_rfc3339());
299 builder.signing_time(time.into())
300 } else {
301 builder
302 };
303
304 let der = builder.build_der()?;
305
306 self.blobs.insert(
307 CodeSigningSlot::Signature,
308 BlobData::BlobWrapper(Box::new(BlobWrapperBlob::from_data_owned(der))),
309 );
310 self.state = BlobsState::SignatureAdded;
311
312 Ok(())
313 }
314
315 pub fn create_empty_cms_signature(&mut self) -> Result<(), AppleCodesignError> {
316 self.blobs.insert(
317 CodeSigningSlot::Signature,
318 BlobData::BlobWrapper(Box::new(BlobWrapperBlob::from_data_owned(Vec::new()))),
319 );
320 self.state = BlobsState::SignatureAdded;
321 Ok(())
322 }
323
324 pub fn add_notarization_ticket(
328 &mut self,
329 ticket_data: Vec<u8>,
330 ) -> Result<(), AppleCodesignError> {
331 self.blobs.insert(
332 CodeSigningSlot::Ticket,
333 BlobData::BlobWrapper(Box::new(BlobWrapperBlob::from_data_owned(ticket_data))),
334 );
335 self.state = BlobsState::TicketAdded;
336
337 Ok(())
338 }
339
340 pub fn create_superblob(&self) -> Result<Vec<u8>, AppleCodesignError> {
342 if matches!(self.state, BlobsState::Empty | BlobsState::SpecialAdded) {
343 return Err(AppleCodesignError::SignatureBuilder(
344 "code directory required in order to materialize superblob",
345 ));
346 }
347
348 let blobs = self
349 .blobs
350 .iter()
351 .map(|(slot, blob)| {
352 let data = blob.to_blob_bytes()?;
353
354 Ok((*slot, data))
355 })
356 .collect::<Result<Vec<_>, AppleCodesignError>>()?;
357
358 create_superblob(CodeSigningMagic::EmbeddedSignature, blobs.iter())
359 }
360}