1use {
19 crate::{
20 format::XarChecksum,
21 reader::XarReader,
22 table_of_contents::{Checksum, ChecksumType, File, KeyInfo, Signature, SignatureStyle},
23 Error, XarResult,
24 },
25 bcder::Oid,
26 cryptographic_message_syntax::{asn1::rfc5652::OID_ID_DATA, SignedDataBuilder, SignerBuilder},
27 flate2::{write::ZlibEncoder, Compression},
28 log::{error, info, warn},
29 rand::RngCore,
30 scroll::IOwrite,
31 std::{
32 cmp::Ordering,
33 collections::HashMap,
34 fmt::Debug,
35 io::{Read, Seek, Write},
36 },
37 url::Url,
38 x509_certificate::{CapturedX509Certificate, KeyInfoSigner},
39};
40
41pub struct XarSigner<R: Read + Seek + Sized + Debug> {
43 reader: XarReader<R>,
44 checksum_type: ChecksumType,
45}
46
47impl<R: Read + Seek + Sized + Debug> XarSigner<R> {
48 pub fn new(reader: XarReader<R>) -> Self {
50 let checksum_type = reader.table_of_contents().checksum.style;
51
52 Self {
53 reader,
54 checksum_type,
55 }
56 }
57
58 pub fn sign<W: Write>(
65 &mut self,
66 writer: &mut W,
67 signing_key: &dyn KeyInfoSigner,
68 signing_cert: &CapturedX509Certificate,
69 time_stamp_url: Option<&Url>,
70 certificates: impl Iterator<Item = CapturedX509Certificate>,
71 ) -> XarResult<()> {
72 let extra_certificates = certificates.collect::<Vec<_>>();
73
74 let chain = std::iter::once(signing_cert)
76 .chain(extra_certificates.iter())
77 .collect::<Vec<_>>();
78
79 let mut random = [0u8; 32];
82 rand::thread_rng().fill_bytes(&mut random);
83 let empty_digest = self.checksum_type.digest_data(&random)?;
84 let digest_size = empty_digest.len() as u64;
85
86 info!("performing empty RSA signature to calculate signature length");
87 let rsa_signature_len = signing_key.try_sign(&empty_digest)?.as_ref().len();
88
89 info!("performing empty CMS signature to calculate data length");
90 let signer =
91 SignerBuilder::new(signing_key, signing_cert.clone()).message_id_content(empty_digest);
92
93 let signer = if let Some(time_stamp_url) = time_stamp_url {
94 info!("using time-stamp server {}", time_stamp_url);
95 signer.time_stamp_url(time_stamp_url.clone())?
96 } else {
97 signer
98 };
99
100 let cms_signature_len = SignedDataBuilder::default()
101 .content_type(Oid(OID_ID_DATA.as_ref().into()))
102 .signer(signer.clone())
103 .certificates(extra_certificates.iter().cloned())
104 .build_der()?
105 .len();
106
107 let cms_signature_len = cms_signature_len + 512;
109
110 let mut toc = self.reader.table_of_contents().clone();
112 toc.checksum = Checksum {
113 style: self.checksum_type,
114 offset: 0,
115 size: digest_size,
116 };
117
118 let rsa_signature = Signature {
119 style: SignatureStyle::Rsa,
120 offset: digest_size,
122 size: rsa_signature_len as _,
123 key_info: KeyInfo::from_certificates(chain.iter().copied())?,
124 };
125
126 let cms_signature = Signature {
127 style: SignatureStyle::Cms,
128 offset: rsa_signature.offset + rsa_signature.size,
130 size: cms_signature_len as _,
131 key_info: KeyInfo::from_certificates(chain.iter().copied())?,
132 };
133
134 let mut current_offset = cms_signature.offset + cms_signature.size;
135
136 toc.signature = Some(rsa_signature);
137 toc.x_signature = Some(cms_signature);
138
139 let mut ids_to_offsets = HashMap::new();
143
144 for (_, file) in self.reader.files()? {
145 if let Some(data) = &file.data {
146 ids_to_offsets.insert(file.id, current_offset);
147 current_offset += data.length;
148 }
149 }
150
151 toc.visit_files_mut(&|file: &mut File| {
152 if let Some(data) = &mut file.data {
153 data.offset = *ids_to_offsets
154 .get(&file.id)
155 .expect("file should have offset recorded");
156 }
157 });
158
159 warn!("generating new XAR table of contents XML");
162 let toc_data = toc.to_xml()?;
163 info!("table of contents size: {}", toc_data.len());
164
165 let mut zlib = ZlibEncoder::new(Vec::new(), Compression::default());
166 zlib.write_all(&toc_data)?;
167 let toc_compressed = zlib.finish()?;
168
169 let toc_digest = self.checksum_type.digest_data(&toc_compressed)?;
170
171 let rsa_signature = signing_key.try_sign(&toc_digest)?;
173
174 let mut cms_signature = SignedDataBuilder::default()
175 .content_type(Oid(OID_ID_DATA.as_ref().into()))
176 .signer(signer.message_id_content(toc_digest.clone()))
177 .certificates(extra_certificates.iter().cloned())
178 .build_der()?;
179
180 match cms_signature.len().cmp(&cms_signature_len) {
181 Ordering::Greater => {
182 error!("real CMS signature overflowed allocated space for signature (please report this bug)");
183 return Err(Error::Unsupported("CMS signature overflow"));
184 }
185 Ordering::Equal => {}
186 Ordering::Less => {
187 cms_signature
188 .extend_from_slice(&b"\0".repeat(cms_signature_len - cms_signature.len()));
189 }
190 }
191
192 let mut header = *self.reader.header();
194 header.checksum_algorithm_id = XarChecksum::from(self.checksum_type).into();
195 header.toc_length_compressed = toc_compressed.len() as _;
196 header.toc_length_uncompressed = toc_data.len() as _;
197
198 writer.iowrite_with(header, scroll::BE)?;
199 writer.write_all(&toc_compressed)?;
200 writer.write_all(&toc_digest)?;
201 writer.write_all(rsa_signature.as_ref())?;
202 writer.write_all(&cms_signature)?;
203
204 for (path, file) in self.reader.files()? {
206 if file.data.is_some() {
207 info!("copying {} to output XAR", path);
208 self.reader.write_file_data_heap_from_file(&file, writer)?;
209 }
210 }
211
212 Ok(())
213 }
214}