use {
crate::{
format::XarChecksum,
reader::XarReader,
table_of_contents::{Checksum, ChecksumType, File, KeyInfo, Signature, SignatureStyle},
Error, XarResult,
},
bcder::Oid,
cryptographic_message_syntax::{asn1::rfc5652::OID_ID_DATA, SignedDataBuilder, SignerBuilder},
flate2::{write::ZlibEncoder, Compression},
log::{error, info, warn},
rand::RngCore,
scroll::IOwrite,
std::{
cmp::Ordering,
collections::HashMap,
fmt::Debug,
io::{Read, Seek, Write},
},
x509_certificate::{CapturedX509Certificate, KeyInfoSigner},
};
pub struct XarSigner<R: Read + Seek + Sized + Debug> {
reader: XarReader<R>,
checksum_type: ChecksumType,
}
impl<R: Read + Seek + Sized + Debug> XarSigner<R> {
pub fn new(reader: XarReader<R>) -> Self {
let checksum_type = reader.table_of_contents().checksum.style;
Self {
reader,
checksum_type,
}
}
pub fn sign<W: Write>(
&mut self,
writer: &mut W,
signing_key: &dyn KeyInfoSigner,
signing_cert: &CapturedX509Certificate,
certificates: impl Iterator<Item = CapturedX509Certificate>,
) -> XarResult<()> {
let extra_certificates = certificates.collect::<Vec<_>>();
let chain = std::iter::once(signing_cert)
.chain(extra_certificates.iter())
.collect::<Vec<_>>();
let mut random = [0u8; 32];
rand::thread_rng().fill_bytes(&mut random);
let empty_digest = self.checksum_type.digest_data(&random)?;
let digest_size = empty_digest.len() as u64;
info!("performing empty RSA signature to calculate signature length");
let rsa_signature_len = signing_key.try_sign(&empty_digest)?.as_ref().len();
info!("performing empty CMS signature to calculate data length");
let signer =
SignerBuilder::new(signing_key, signing_cert.clone()).message_id_content(empty_digest);
let cms_signature_len = SignedDataBuilder::default()
.content_type(Oid(OID_ID_DATA.as_ref().into()))
.signer(signer.clone())
.certificates(extra_certificates.iter().cloned())
.build_der()?
.len();
let cms_signature_len = cms_signature_len + 512;
let mut toc = self.reader.table_of_contents().clone();
toc.checksum = Checksum {
style: self.checksum_type,
offset: 0,
size: digest_size,
};
let rsa_signature = Signature {
style: SignatureStyle::Rsa,
offset: digest_size,
size: rsa_signature_len as _,
key_info: KeyInfo::from_certificates(chain.iter().copied())?,
};
let cms_signature = Signature {
style: SignatureStyle::Cms,
offset: rsa_signature.offset + rsa_signature.size,
size: cms_signature_len as _,
key_info: KeyInfo::from_certificates(chain.iter().copied())?,
};
let mut current_offset = cms_signature.offset + cms_signature.size;
toc.signature = Some(rsa_signature);
toc.x_signature = Some(cms_signature);
let mut ids_to_offsets = HashMap::new();
for (_, file) in self.reader.files()? {
if let Some(data) = &file.data {
ids_to_offsets.insert(file.id, current_offset);
current_offset += data.length;
}
}
toc.visit_files_mut(&|file: &mut File| {
if let Some(data) = &mut file.data {
data.offset = *ids_to_offsets
.get(&file.id)
.expect("file should have offset recorded");
}
});
warn!("generating new XAR table of contents XML");
let toc_data = toc.to_xml()?;
info!("table of contents size: {}", toc_data.len());
let mut zlib = ZlibEncoder::new(Vec::new(), Compression::default());
zlib.write_all(&toc_data)?;
let toc_compressed = zlib.finish()?;
let toc_digest = self.checksum_type.digest_data(&toc_compressed)?;
let rsa_signature = signing_key.try_sign(&toc_digest)?;
let mut cms_signature = SignedDataBuilder::default()
.content_type(Oid(OID_ID_DATA.as_ref().into()))
.signer(signer.message_id_content(toc_digest.clone()))
.certificates(extra_certificates.iter().cloned())
.build_der()?;
match cms_signature.len().cmp(&cms_signature_len) {
Ordering::Greater => {
error!("real CMS signature overflowed allocated space for signature (please report this bug)");
return Err(Error::Unsupported("CMS signature overflow"));
}
Ordering::Equal => {}
Ordering::Less => {
cms_signature
.extend_from_slice(&b"\0".repeat(cms_signature_len - cms_signature.len()));
}
}
let mut header = *self.reader.header();
header.checksum_algorithm_id = XarChecksum::from(self.checksum_type).into();
header.toc_length_compressed = toc_compressed.len() as _;
header.toc_length_uncompressed = toc_data.len() as _;
writer.iowrite_with(header, scroll::BE)?;
writer.write_all(&toc_compressed)?;
writer.write_all(&toc_digest)?;
writer.write_all(rsa_signature.as_ref())?;
writer.write_all(&cms_signature)?;
for (path, file) in self.reader.files()? {
if file.data.is_some() {
info!("copying {} to output XAR", path);
self.reader.write_file_data_heap_from_file(&file, writer)?;
}
}
Ok(())
}
}