1use {
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)]
38enum BlobsState {
39 Empty,
40 SpecialAdded,
41 CodeDirectoryAdded,
42 SignatureAdded,
43 TicketAdded,
44}
45
46impl Default for BlobsState {
47 fn default() -> Self {
48 Self::Empty
49 }
50}
51
52#[derive(Debug, Default)]
56pub struct EmbeddedSignatureBuilder<'a> {
57 state: BlobsState,
58 blobs: BTreeMap<CodeSigningSlot, BlobData<'a>>,
59}
60
61impl<'a> EmbeddedSignatureBuilder<'a> {
62 pub fn new_for_stapling(signature: EmbeddedSignature<'a>) -> Result<Self, AppleCodesignError> {
68 let blobs = signature
69 .blobs
70 .into_iter()
71 .map(|blob| {
72 let parsed = blob.into_parsed_blob()?;
73
74 Ok((parsed.blob_entry.slot, parsed.blob))
75 })
76 .collect::<Result<BTreeMap<_, _>, AppleCodesignError>>()?;
77
78 Ok(Self {
79 state: BlobsState::CodeDirectoryAdded,
80 blobs,
81 })
82 }
83
84 pub fn code_directory(&self) -> Option<&CodeDirectoryBlob> {
86 self.blobs.get(&CodeSigningSlot::CodeDirectory).map(|blob| {
87 if let BlobData::CodeDirectory(cd) = blob {
88 (*cd).as_ref()
89 } else {
90 panic!("a non code directory should never be stored in the code directory slot");
91 }
92 })
93 }
94
95 pub fn add_blob(
104 &mut self,
105 slot: CodeSigningSlot,
106 blob: BlobData<'a>,
107 ) -> Result<(), AppleCodesignError> {
108 match self.state {
109 BlobsState::Empty | BlobsState::SpecialAdded => {}
110 BlobsState::CodeDirectoryAdded
111 | BlobsState::SignatureAdded
112 | BlobsState::TicketAdded => {
113 return Err(AppleCodesignError::SignatureBuilder(
114 "cannot add blobs after code directory or signature is registered",
115 ));
116 }
117 }
118
119 if matches!(
120 blob,
121 BlobData::CodeDirectory(_)
122 | BlobData::EmbeddedSignature(_)
123 | BlobData::EmbeddedSignatureOld(_)
124 ) {
125 return Err(AppleCodesignError::SignatureBuilder(
126 "cannot register code directory or signature blob via add_blob()",
127 ));
128 }
129
130 self.blobs.insert(slot, blob);
131
132 self.state = BlobsState::SpecialAdded;
133
134 Ok(())
135 }
136
137 pub fn add_code_directory(
149 &mut self,
150 cd_slot: CodeSigningSlot,
151 mut cd: CodeDirectoryBlob<'a>,
152 ) -> Result<&CodeDirectoryBlob, AppleCodesignError> {
153 if matches!(self.state, BlobsState::SignatureAdded) {
154 return Err(AppleCodesignError::SignatureBuilder(
155 "cannot add code directory after signature data added",
156 ));
157 }
158
159 for (slot, blob) in &self.blobs {
160 if !slot.is_code_directory_specials_expressible() {
162 continue;
163 }
164
165 let digest = blob.digest_with(cd.digest_type)?;
166
167 cd.set_slot_digest(*slot, digest)?;
168 }
169
170 self.blobs.insert(cd_slot, cd.into());
171 self.state = BlobsState::CodeDirectoryAdded;
172
173 Ok(self.code_directory().expect("we just inserted this key"))
174 }
175
176 pub fn add_alternative_code_directory(
181 &mut self,
182 cd: CodeDirectoryBlob<'a>,
183 ) -> Result<&CodeDirectoryBlob, AppleCodesignError> {
184 let mut our_slot = CodeSigningSlot::AlternateCodeDirectory0;
185
186 for slot in self.blobs.keys() {
187 if slot.is_alternative_code_directory() {
188 our_slot = CodeSigningSlot::from(u32::from(*slot) + 1);
189
190 if !our_slot.is_alternative_code_directory() {
191 return Err(AppleCodesignError::SignatureBuilder(
192 "no more available alternative code directory slots",
193 ));
194 }
195 }
196 }
197
198 self.add_code_directory(our_slot, cd)
199 }
200
201 pub fn create_cms_signature(
216 &mut self,
217 signing_key: &dyn KeyInfoSigner,
218 signing_cert: &CapturedX509Certificate,
219 time_stamp_url: Option<&Url>,
220 certificates: impl Iterator<Item = CapturedX509Certificate>,
221 signing_time: Option<chrono::DateTime<chrono::Utc>>,
222 ) -> Result<(), AppleCodesignError> {
223 let main_cd = self
224 .code_directory()
225 .ok_or(AppleCodesignError::SignatureBuilder(
226 "cannot create CMS signature unless code directory is present",
227 ))?;
228
229 if let Some(cn) = signing_cert.subject_common_name() {
230 warn!("creating cryptographic signature with certificate {}", cn);
231 }
232
233 let mut cdhashes = vec![];
234 let mut attributes = vec![];
235
236 for (slot, blob) in &self.blobs {
237 if *slot == CodeSigningSlot::CodeDirectory || slot.is_alternative_code_directory() {
238 if let BlobData::CodeDirectory(cd) = blob {
239 let mut digest = cd.digest_with(cd.digest_type)?;
242 digest.truncate(20);
243 cdhashes.push(plist::Value::Data(digest));
244
245 let digest = cd.digest_with(cd.digest_type)?;
248 let alg = DigestAlgorithm::try_from(cd.digest_type)?;
249
250 attributes.push(AttributeValue::new(bcder::Captured::from_values(
251 bcder::Mode::Der,
252 bcder::encode::sequence((
253 Oid::from(alg).encode_ref(),
254 bcder::OctetString::new(digest.into()).encode_ref(),
255 )),
256 )));
257 } else {
258 return Err(AppleCodesignError::SignatureBuilder(
259 "unexpected blob type in code directory slot",
260 ));
261 }
262 }
263 }
264
265 let mut plist_dict = plist::Dictionary::new();
266 plist_dict.insert("cdhashes".to_string(), plist::Value::Array(cdhashes));
267
268 let mut plist_xml = vec![];
269 plist::Value::from(plist_dict)
270 .to_writer_xml(&mut plist_xml)
271 .map_err(AppleCodesignError::CodeDirectoryPlist)?;
272 plist_xml.push(b'\n');
275
276 let signer = SignerBuilder::new(signing_key, signing_cert.clone())
277 .message_id_content(main_cd.to_blob_bytes()?)
278 .signed_attribute_octet_string(
279 Oid(Bytes::copy_from_slice(CD_DIGESTS_PLIST_OID.as_ref())),
280 &plist_xml,
281 );
282
283 let signer = signer.signed_attribute(Oid(CD_DIGESTS_OID.as_ref().into()), attributes);
284
285 let signer = if let Some(time_stamp_url) = time_stamp_url {
286 info!("Using time-stamp server {}", time_stamp_url);
287 signer.time_stamp_url(time_stamp_url.clone())?
288 } else {
289 signer
290 };
291
292 let builder = SignedDataBuilder::default()
293 .content_type(Oid(OID_ID_DATA.as_ref().into()))
297 .signer(signer)
298 .certificates(certificates);
299
300 let builder = if let Some(time) = signing_time {
301 info!("Using signing time {}", time.to_rfc3339());
302 builder.signing_time(time.into())
303 } else {
304 builder
305 };
306
307 let der = builder.build_der()?;
308
309 self.blobs.insert(
310 CodeSigningSlot::Signature,
311 BlobData::BlobWrapper(Box::new(BlobWrapperBlob::from_data_owned(der))),
312 );
313 self.state = BlobsState::SignatureAdded;
314
315 Ok(())
316 }
317
318 pub fn create_empty_cms_signature(&mut self) -> Result<(), AppleCodesignError> {
319 self.blobs.insert(
320 CodeSigningSlot::Signature,
321 BlobData::BlobWrapper(Box::new(BlobWrapperBlob::from_data_owned(Vec::new()))),
322 );
323 self.state = BlobsState::SignatureAdded;
324 Ok(())
325 }
326
327 pub fn add_notarization_ticket(
331 &mut self,
332 ticket_data: Vec<u8>,
333 ) -> Result<(), AppleCodesignError> {
334 self.blobs.insert(
335 CodeSigningSlot::Ticket,
336 BlobData::BlobWrapper(Box::new(BlobWrapperBlob::from_data_owned(ticket_data))),
337 );
338 self.state = BlobsState::TicketAdded;
339
340 Ok(())
341 }
342
343 pub fn create_superblob(&self) -> Result<Vec<u8>, AppleCodesignError> {
345 if matches!(self.state, BlobsState::Empty | BlobsState::SpecialAdded) {
346 return Err(AppleCodesignError::SignatureBuilder(
347 "code directory required in order to materialize superblob",
348 ));
349 }
350
351 let blobs = self
352 .blobs
353 .iter()
354 .map(|(slot, blob)| {
355 let data = blob.to_blob_bytes()?;
356
357 Ok((*slot, data))
358 })
359 .collect::<Result<Vec<_>, AppleCodesignError>>()?;
360
361 create_superblob(CodeSigningMagic::EmbeddedSignature, blobs.iter())
362 }
363}