use {
crate::{
code_directory::CodeDirectoryBlob,
embedded_signature::{
create_superblob, Blob, BlobData, BlobWrapperBlob, CodeSigningMagic, CodeSigningSlot,
EmbeddedSignature,
},
error::AppleCodesignError,
},
bcder::{encode::PrimitiveContent, Oid},
bytes::Bytes,
cryptographic_message_syntax::{asn1::rfc5652::OID_ID_DATA, SignedDataBuilder, SignerBuilder},
log::{info, warn},
reqwest::Url,
std::collections::BTreeMap,
x509_certificate::{
rfc5652::AttributeValue, CapturedX509Certificate, DigestAlgorithm, KeyInfoSigner,
},
};
pub const CD_DIGESTS_PLIST_OID: bcder::ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 9, 1]);
pub const CD_DIGESTS_OID: bcder::ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 9, 2]);
#[derive(Clone, Copy, Debug, PartialEq)]
enum BlobsState {
Empty,
SpecialAdded,
CodeDirectoryAdded,
SignatureAdded,
TicketAdded,
}
impl Default for BlobsState {
fn default() -> Self {
Self::Empty
}
}
#[derive(Debug, Default)]
pub struct EmbeddedSignatureBuilder<'a> {
state: BlobsState,
blobs: BTreeMap<CodeSigningSlot, BlobData<'a>>,
}
impl<'a> EmbeddedSignatureBuilder<'a> {
pub fn new_for_stapling(signature: EmbeddedSignature<'a>) -> Result<Self, AppleCodesignError> {
let blobs = signature
.blobs
.into_iter()
.map(|blob| {
let parsed = blob.into_parsed_blob()?;
Ok((parsed.blob_entry.slot, parsed.blob))
})
.collect::<Result<BTreeMap<_, _>, AppleCodesignError>>()?;
Ok(Self {
state: BlobsState::CodeDirectoryAdded,
blobs,
})
}
pub fn code_directory(&self) -> Option<&CodeDirectoryBlob> {
self.blobs.get(&CodeSigningSlot::CodeDirectory).map(|blob| {
if let BlobData::CodeDirectory(cd) = blob {
(*cd).as_ref()
} else {
panic!("a non code directory should never be stored in the code directory slot");
}
})
}
pub fn add_blob(
&mut self,
slot: CodeSigningSlot,
blob: BlobData<'a>,
) -> Result<(), AppleCodesignError> {
match self.state {
BlobsState::Empty | BlobsState::SpecialAdded => {}
BlobsState::CodeDirectoryAdded
| BlobsState::SignatureAdded
| BlobsState::TicketAdded => {
return Err(AppleCodesignError::SignatureBuilder(
"cannot add blobs after code directory or signature is registered",
));
}
}
if matches!(
blob,
BlobData::CodeDirectory(_)
| BlobData::EmbeddedSignature(_)
| BlobData::EmbeddedSignatureOld(_)
) {
return Err(AppleCodesignError::SignatureBuilder(
"cannot register code directory or signature blob via add_blob()",
));
}
self.blobs.insert(slot, blob);
self.state = BlobsState::SpecialAdded;
Ok(())
}
pub fn add_code_directory(
&mut self,
cd_slot: CodeSigningSlot,
mut cd: CodeDirectoryBlob<'a>,
) -> Result<&CodeDirectoryBlob, AppleCodesignError> {
if matches!(self.state, BlobsState::SignatureAdded) {
return Err(AppleCodesignError::SignatureBuilder(
"cannot add code directory after signature data added",
));
}
for (slot, blob) in &self.blobs {
if !slot.is_code_directory_specials_expressible() {
continue;
}
let digest = blob.digest_with(cd.digest_type)?;
cd.set_slot_digest(*slot, digest)?;
}
self.blobs.insert(cd_slot, cd.into());
self.state = BlobsState::CodeDirectoryAdded;
Ok(self.code_directory().expect("we just inserted this key"))
}
pub fn add_alternative_code_directory(
&mut self,
cd: CodeDirectoryBlob<'a>,
) -> Result<&CodeDirectoryBlob, AppleCodesignError> {
let mut our_slot = CodeSigningSlot::AlternateCodeDirectory0;
for slot in self.blobs.keys() {
if slot.is_alternative_code_directory() {
our_slot = CodeSigningSlot::from(u32::from(*slot) + 1);
if !our_slot.is_alternative_code_directory() {
return Err(AppleCodesignError::SignatureBuilder(
"no more available alternative code directory slots",
));
}
}
}
self.add_code_directory(our_slot, cd)
}
pub fn create_cms_signature(
&mut self,
signing_key: &dyn KeyInfoSigner,
signing_cert: &CapturedX509Certificate,
time_stamp_url: Option<&Url>,
certificates: impl Iterator<Item = CapturedX509Certificate>,
) -> Result<(), AppleCodesignError> {
let main_cd = self
.code_directory()
.ok_or(AppleCodesignError::SignatureBuilder(
"cannot create CMS signature unless code directory is present",
))?;
if let Some(cn) = signing_cert.subject_common_name() {
warn!("creating cryptographic signature with certificate {}", cn);
}
let mut cdhashes = vec![];
let mut attributes = vec![];
for (slot, blob) in &self.blobs {
if *slot == CodeSigningSlot::CodeDirectory || slot.is_alternative_code_directory() {
if let BlobData::CodeDirectory(cd) = blob {
let mut digest = cd.digest_with(cd.digest_type)?;
digest.truncate(20);
cdhashes.push(plist::Value::Data(digest));
let digest = cd.digest_with(cd.digest_type)?;
let alg = DigestAlgorithm::try_from(cd.digest_type)?;
attributes.push(AttributeValue::new(bcder::Captured::from_values(
bcder::Mode::Der,
bcder::encode::sequence((
Oid::from(alg).encode_ref(),
bcder::OctetString::new(digest.into()).encode_ref(),
)),
)));
} else {
return Err(AppleCodesignError::SignatureBuilder(
"unexpected blob type in code directory slot",
));
}
}
}
let mut plist_dict = plist::Dictionary::new();
plist_dict.insert("cdhashes".to_string(), plist::Value::Array(cdhashes));
let mut plist_xml = vec![];
plist::Value::from(plist_dict)
.to_writer_xml(&mut plist_xml)
.map_err(AppleCodesignError::CodeDirectoryPlist)?;
plist_xml.push(b'\n');
let signer = SignerBuilder::new(signing_key, signing_cert.clone())
.message_id_content(main_cd.to_blob_bytes()?)
.signed_attribute_octet_string(
Oid(Bytes::copy_from_slice(CD_DIGESTS_PLIST_OID.as_ref())),
&plist_xml,
);
let signer = signer.signed_attribute(Oid(CD_DIGESTS_OID.as_ref().into()), attributes);
let signer = if let Some(time_stamp_url) = time_stamp_url {
info!("Using time-stamp server {}", time_stamp_url);
signer.time_stamp_url(time_stamp_url.clone())?
} else {
signer
};
let der = SignedDataBuilder::default()
.content_type(Oid(OID_ID_DATA.as_ref().into()))
.signer(signer)
.certificates(certificates)
.build_der()?;
self.blobs.insert(
CodeSigningSlot::Signature,
BlobData::BlobWrapper(Box::new(BlobWrapperBlob::from_data_owned(der))),
);
self.state = BlobsState::SignatureAdded;
Ok(())
}
pub fn add_notarization_ticket(
&mut self,
ticket_data: Vec<u8>,
) -> Result<(), AppleCodesignError> {
self.blobs.insert(
CodeSigningSlot::Ticket,
BlobData::BlobWrapper(Box::new(BlobWrapperBlob::from_data_owned(ticket_data))),
);
self.state = BlobsState::TicketAdded;
Ok(())
}
pub fn create_superblob(&self) -> Result<Vec<u8>, AppleCodesignError> {
if matches!(self.state, BlobsState::Empty | BlobsState::SpecialAdded) {
return Err(AppleCodesignError::SignatureBuilder(
"code directory required in order to materialize superblob",
));
}
let blobs = self
.blobs
.iter()
.map(|(slot, blob)| {
let data = blob.to_blob_bytes()?;
Ok((*slot, data))
})
.collect::<Result<Vec<_>, AppleCodesignError>>()?;
create_superblob(CodeSigningMagic::EmbeddedSignature, blobs.iter())
}
}