1use std::collections::{HashMap, HashSet};
5use std::io::{self, Read, Seek};
6
7use base64::Engine;
8use base64::engine::general_purpose::STANDARD as BASE64;
9use cms::cert::{CertificateChoices, IssuerAndSerialNumber};
10use cms::content_info::{CmsVersion, ContentInfo};
11use cms::signed_data::{
12 CertificateSet, EncapsulatedContentInfo, SignedData, SignerIdentifier, SignerInfo, SignerInfos,
13};
14use der::asn1::{Any, Null, ObjectIdentifier, OctetString, SetOfVec};
15use der::{Decode, Encode, EncodingRules};
16use x509_cert::spki::AlgorithmIdentifierOwned;
17use zip::ZipArchive;
18
19use super::{
20 Certificate, DigestAlgorithm, KeyError, KeyKind, Scheme, SignError, SignatureError, SigningIdentity, VerifyError,
21};
22use crate::archive::is_signed_entry;
23use crate::archive::{ZipError, entry_name, read_capped};
24use crate::types::ArchivePath;
25
26const MANIFEST: &str = "META-INF/MANIFEST.MF";
27const MAX_LINE: usize = 72;
28const SIGNATURE_FILE_LIMIT: u64 = 64 << 20;
30
31const ID_DATA: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.7.1");
32const ID_SIGNED_DATA: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.7.2");
33const ID_CONTENT_TYPE: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.9.3");
34const ID_MESSAGE_DIGEST: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.9.4");
35const RSA_ENCRYPTION: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.1");
36const SHA1_WITH_RSA: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.5");
37const SHA256_WITH_RSA: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.11");
38const SHA384_WITH_RSA: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.12");
39const SHA512_WITH_RSA: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.13");
40const EC_PUBLIC_KEY: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.2.1");
41const ECDSA_WITH_SHA1: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.4.1");
42const ECDSA_WITH_SHA256: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.2");
43const ECDSA_WITH_SHA384: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.3");
44const ECDSA_WITH_SHA512: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.4");
45const RSA_ALGORITHMS: [(ObjectIdentifier, Option<DigestAlgorithm>); 5] = [
47 (RSA_ENCRYPTION, None),
48 (SHA1_WITH_RSA, Some(DigestAlgorithm::Sha1)),
49 (SHA256_WITH_RSA, Some(DigestAlgorithm::Sha256)),
50 (SHA384_WITH_RSA, Some(DigestAlgorithm::Sha384)),
51 (SHA512_WITH_RSA, Some(DigestAlgorithm::Sha512)),
52];
53const EC_ALGORITHMS: [(ObjectIdentifier, Option<DigestAlgorithm>); 5] = [
54 (EC_PUBLIC_KEY, None),
55 (ECDSA_WITH_SHA1, Some(DigestAlgorithm::Sha1)),
56 (ECDSA_WITH_SHA256, Some(DigestAlgorithm::Sha256)),
57 (ECDSA_WITH_SHA384, Some(DigestAlgorithm::Sha384)),
58 (ECDSA_WITH_SHA512, Some(DigestAlgorithm::Sha512)),
59];
60
61#[derive(Debug, thiserror::Error)]
63#[non_exhaustive]
64pub enum JarError {
65 #[error("META-INF/MANIFEST.MF is missing")]
66 MissingManifest,
67 #[error("{file} is malformed at line {line}")]
68 MalformedManifest { file: String, line: usize },
69 #[error("{file} has more than one section for {name:?}")]
70 DuplicateSection { file: String, name: String },
71 #[error("signature block {block} has no signature file")]
72 MissingSignatureFile { block: String },
73 #[error("signature block {file} is malformed: {source}")]
74 MalformedSignatureBlock { file: String, source: der::Error },
75 #[error("signature block {file} is not a detached signature by exactly one signer")]
76 UnsupportedSignatureBlock { file: String },
77 #[error("signature block {file} does not contain its signer's certificate")]
78 UnknownSignerCertificate { file: String },
79 #[error("signature block {file} names signature algorithm {algorithm}, which does not fit its key and digest")]
80 SignatureAlgorithmMismatch { file: String, algorithm: ObjectIdentifier },
81 #[error("signature block {file} has signed attributes that do not match its signature file")]
82 SignedAttributesMismatch { file: String },
83 #[error("signature block {file} does not verify: {source}")]
84 BadSignature { file: String, source: SignatureError },
85 #[error("{file} uses no supported digest algorithm (SHA-1, SHA-256, SHA-384, or SHA-512)")]
86 UnsupportedDigest { file: String },
87 #[error("{file} has a digest that is not valid base64")]
88 InvalidDigest { file: String },
89 #[error("signature file {signature_file} does not match MANIFEST.MF")]
90 ManifestDigestMismatch { signature_file: String },
91 #[error("entry {entry:?} is not covered by MANIFEST.MF")]
92 UnsignedEntry { entry: String },
93 #[error("MANIFEST.MF lists {entry:?}, which is not in the APK")]
94 MissingEntry { entry: String },
95 #[error("entry {entry:?} does not match its digest in MANIFEST.MF")]
96 EntryDigestMismatch { entry: String },
97}
98
99pub(crate) fn sign(
103 entries: &[(String, Vec<u8>)],
104 digest: DigestAlgorithm,
105 identities: &[SigningIdentity],
106 declared: &[Scheme],
107) -> Result<Vec<(ArchivePath, Vec<u8>)>, SignError> {
108 let digest_name = format!("{}-Digest", digest.jar_name());
109 let mut signed: Vec<&(String, Vec<u8>)> = entries.iter().collect();
110 signed.sort_by(|a, b| a.0.cmp(&b.0));
111
112 let mut manifest = section(&[("Manifest-Version", "1.0"), ("Created-By", "1.0 (Android)")]);
113 let mut sections = Vec::with_capacity(signed.len());
114 for (name, entry_digest) in signed {
115 let entry_section = section(&[("Name", name), (&digest_name, &BASE64.encode(entry_digest))]);
116 manifest.extend_from_slice(&entry_section);
117 sections.push((name, entry_section));
118 }
119
120 let manifest_digest = BASE64.encode(digest.digest(&manifest));
121 let manifest_attribute = format!("{}-Digest-Manifest", digest.jar_name());
122 let declared = declared.iter().map(|s| s.number().to_string()).collect::<Vec<_>>().join(", ");
123 let mut main = vec![("Signature-Version", "1.0"), ("Created-By", "1.0 (Android)")];
124 main.push((&manifest_attribute, &manifest_digest));
125 if !declared.is_empty() {
126 main.push(("X-Android-APK-Signed", &declared));
127 }
128 let mut signature_file = section(&main);
129 for (name, entry_section) in §ions {
130 signature_file.extend(section(&[("Name", name), (&digest_name, &BASE64.encode(digest.digest(entry_section)))]));
131 }
132
133 let mut files = vec![(ArchivePath::known(MANIFEST), manifest)];
134 for (index, identity) in identities.iter().enumerate() {
135 let base = if identities.len() == 1 { "CERT".to_owned() } else { format!("CERT{}", index + 1) };
136 let extension = match identity.key().kind() {
137 KeyKind::Rsa { .. } => "RSA",
138 KeyKind::EcP256 | KeyKind::EcP384 => "EC",
139 };
140 let block = signature_block(&signature_file, identity, digest).map_err(SignError::Key)?;
141 files.push((ArchivePath::known(&format!("META-INF/{base}.SF")), signature_file.clone()));
142 files.push((ArchivePath::known(&format!("META-INF/{base}.{extension}")), block));
143 }
144 Ok(files)
145}
146
147fn section(attributes: &[(&str, &str)]) -> Vec<u8> {
149 let mut out = Vec::new();
150 for (name, value) in attributes {
151 let line = format!("{name}: {value}");
152 let mut bytes = line.as_bytes();
153 let mut limit = MAX_LINE;
154 loop {
155 let (chunk, rest) = bytes.split_at(bytes.len().min(limit));
156 out.extend_from_slice(chunk);
157 out.extend_from_slice(b"\r\n");
158 if rest.is_empty() {
159 break;
160 }
161 out.push(b' ');
162 bytes = rest;
163 limit = MAX_LINE - 1;
164 }
165 }
166 out.extend_from_slice(b"\r\n");
167 out
168}
169
170fn signature_algorithm(key: KeyKind, digest: DigestAlgorithm) -> Result<AlgorithmIdentifierOwned, der::Error> {
172 Ok(match key {
173 KeyKind::Rsa { .. } => {
174 AlgorithmIdentifierOwned { oid: RSA_ENCRYPTION, parameters: Some(Any::encode_from(&Null)?) }
175 }
176 KeyKind::EcP256 | KeyKind::EcP384 => {
177 let (oid, _) = EC_ALGORITHMS.into_iter().find(|&(_, named)| named == Some(digest)).expect("every digest");
178 AlgorithmIdentifierOwned { oid, parameters: None }
179 }
180 })
181}
182
183fn algorithm_fits(algorithm: &ObjectIdentifier, key: KeyKind, digest: DigestAlgorithm) -> bool {
187 let family = match key {
188 KeyKind::Rsa { .. } => RSA_ALGORITHMS,
189 KeyKind::EcP256 | KeyKind::EcP384 => EC_ALGORITHMS,
190 };
191 family.into_iter().any(|(oid, named)| oid == *algorithm && named.is_none_or(|named| named == digest))
192}
193
194fn signature_block(
197 signature_file: &[u8],
198 identity: &SigningIdentity,
199 digest: DigestAlgorithm,
200) -> Result<Vec<u8>, KeyError> {
201 let signature = identity.key().sign_jar(digest, signature_file)?;
202 let leaf = identity.certificate().x509().tbs_certificate();
203 let digest_algorithm = AlgorithmIdentifierOwned {
204 oid: digest.oid(),
205 parameters: Some(Any::encode_from(&Null).map_err(KeyError::Encoding)?),
206 };
207 let signer = SignerInfo {
208 version: CmsVersion::V1,
209 sid: SignerIdentifier::IssuerAndSerialNumber(IssuerAndSerialNumber {
210 issuer: leaf.issuer().clone(),
211 serial_number: leaf.serial_number().clone(),
212 }),
213 digest_alg: digest_algorithm.clone(),
214 signed_attrs: None,
215 signature_algorithm: signature_algorithm(identity.key().kind(), digest).map_err(KeyError::Encoding)?,
216 signature: OctetString::new(signature).map_err(KeyError::Encoding)?,
217 unsigned_attrs: None,
218 };
219 let certificates: Vec<CertificateChoices> =
220 identity.chain().iter().map(|c| CertificateChoices::Certificate(c.x509().clone())).collect();
221 let signed = SignedData {
222 version: CmsVersion::V1,
223 digest_algorithms: SetOfVec::try_from(vec![digest_algorithm]).map_err(KeyError::Encoding)?,
224 encap_content_info: EncapsulatedContentInfo { econtent_type: ID_DATA, econtent: None },
225 certificates: Some(CertificateSet(SetOfVec::try_from(certificates).map_err(KeyError::Encoding)?)),
226 crls: None,
227 signer_infos: SignerInfos(SetOfVec::try_from(vec![signer]).map_err(KeyError::Encoding)?),
228 };
229 let content = Any::encode_from(&signed).map_err(KeyError::Encoding)?;
230 ContentInfo { content_type: ID_SIGNED_DATA, content }.to_der().map_err(KeyError::Encoding)
231}
232
233pub(crate) struct JarSignature {
235 pub signers: Vec<Vec<Certificate>>,
237 pub declared_schemes: Vec<u32>,
239}
240
241fn is_signature_block(name: &str) -> bool {
243 let Some(file) = name.strip_prefix("META-INF/").filter(|f| !f.contains('/')) else { return false };
244 let file = file.to_ascii_uppercase();
245 [".RSA", ".DSA", ".EC"].iter().any(|extension| file.ends_with(extension))
246}
247
248pub(crate) fn verify<R: Read + Seek>(archive: &mut ZipArchive<R>) -> Result<Option<JarSignature>, VerifyError> {
253 let names: HashMap<String, usize> =
254 (0..archive.len()).map(|i| entry_name(archive, i).map(|name| (name, i))).collect::<Result<_, _>>()?;
255 let mut blocks: Vec<&String> = names.keys().filter(|name| is_signature_block(name)).collect();
256 if blocks.is_empty() {
257 return Ok(None);
258 }
259 blocks.sort();
260 let mut read = |name: &str| -> Result<Option<Vec<u8>>, VerifyError> {
261 let Some(&index) = names.get(name) else { return Ok(None) };
262 Ok(Some(read_capped(archive.by_index(index).map_err(ZipError::from)?, name, SIGNATURE_FILE_LIMIT)?))
263 };
264 let manifest_bytes = read(MANIFEST)?.ok_or(JarError::MissingManifest)?;
265 let manifest = Manifest::parse(&manifest_bytes, MANIFEST)?;
266 let mut signed_entries: Vec<(&String, usize)> =
267 names.iter().filter(|(name, _)| is_signed_entry(name)).map(|(name, &index)| (name, index)).collect();
268 signed_entries.sort();
269
270 let mut signers = Vec::with_capacity(blocks.len());
271 let mut declared_schemes = Vec::new();
272 for block_name in blocks {
273 let base = &block_name[..block_name.rfind('.').expect("blocks have extensions")];
274 let signature_file = format!("{base}.SF");
275 let signature_bytes =
276 read(&signature_file)?.ok_or_else(|| JarError::MissingSignatureFile { block: block_name.clone() })?;
277 let block = read(block_name)?.unwrap_or_default();
278 signers.push(verify_block(&block, &signature_bytes, block_name)?);
279 let signature = Manifest::parse(&signature_bytes, &signature_file)?;
280 check_signature_file(&signature, &manifest, &manifest_bytes, &signature_file)?;
281 if let Some((entry, _)) = signed_entries.iter().find(|(name, _)| signature.entry(name).is_none()) {
283 return Err(JarError::UnsignedEntry { entry: (*entry).clone() }.into());
284 }
285 if let Some(schemes) = signature.main.get("X-Android-APK-Signed") {
286 declared_schemes.extend(schemes.split(',').filter_map(|s| s.trim().parse::<u32>().ok()));
287 }
288 }
289
290 for (name, index) in signed_entries {
291 let section = manifest.entry(name).ok_or_else(|| JarError::UnsignedEntry { entry: name.clone() })?;
292 let (algorithm, expected) = section
293 .digest("-Digest", MANIFEST)?
294 .ok_or_else(|| JarError::UnsupportedDigest { file: MANIFEST.into() })?;
295 let mut hasher = algorithm.hasher();
296 io::copy(&mut archive.by_index(index).map_err(ZipError::from)?, &mut hasher).map_err(ZipError::from)?;
297 if hasher.finish() != expected {
298 return Err(JarError::EntryDigestMismatch { entry: name.clone() }.into());
299 }
300 }
301 if let Some(missing) = manifest.entries.iter().filter_map(Section::name).find(|n| !names.contains_key(*n)) {
302 return Err(JarError::MissingEntry { entry: missing.to_owned() }.into());
303 }
304 Ok(Some(JarSignature { signers, declared_schemes }))
305}
306
307fn verify_block(block: &[u8], signature_file: &[u8], file: &str) -> Result<Vec<Certificate>, JarError> {
310 let malformed = |source| JarError::MalformedSignatureBlock { file: file.to_owned(), source };
311 let unsupported = || JarError::UnsupportedSignatureBlock { file: file.to_owned() };
312 let info = ContentInfo::from_ber(block).map_err(malformed)?;
314 if info.content_type != ID_SIGNED_DATA {
315 return Err(unsupported());
316 }
317 let signed: SignedData = info.content.decode_as_encoding(EncodingRules::Ber).map_err(malformed)?;
318 let content = &signed.encap_content_info;
319 if content.econtent_type != ID_DATA || content.econtent.is_some() {
320 return Err(unsupported());
321 }
322 let [signer] = signed.signer_infos.0.as_slice() else { return Err(unsupported()) };
323 let SignerIdentifier::IssuerAndSerialNumber(id) = &signer.sid else { return Err(unsupported()) };
324 let certificates: Vec<&x509_cert::Certificate> = signed
325 .certificates
326 .iter()
327 .flat_map(|set| set.0.iter())
328 .filter_map(|choice| match choice {
329 CertificateChoices::Certificate(certificate) => Some(certificate),
330 CertificateChoices::Other(_) => None,
331 })
332 .collect();
333 let position = certificates
334 .iter()
335 .position(|c| {
336 c.tbs_certificate().issuer() == &id.issuer && c.tbs_certificate().serial_number() == &id.serial_number
337 })
338 .ok_or_else(|| JarError::UnknownSignerCertificate { file: file.to_owned() })?;
339 let digest = DigestAlgorithm::from_oid(&signer.digest_alg.oid)
340 .ok_or_else(|| JarError::UnsupportedDigest { file: file.to_owned() })?;
341
342 let to_certificate = |c: &x509_cert::Certificate| -> Result<Certificate, JarError> {
343 Certificate::from_der(&c.to_der().map_err(malformed)?).map_err(|_| unsupported())
344 };
345 let leaf = to_certificate(certificates[position])?;
346 let key = leaf.public_key().map_err(|_| unsupported())?;
347 let algorithm = &signer.signature_algorithm.oid;
348 if !algorithm_fits(algorithm, key.kind(), digest) {
349 return Err(JarError::SignatureAlgorithmMismatch { file: file.to_owned(), algorithm: *algorithm });
350 }
351
352 let signed_attributes;
353 let message = match &signer.signed_attrs {
354 None => signature_file,
355 Some(attributes) => {
356 let value_of = |oid| attributes.iter().find(|a| a.oid == oid).and_then(|a| a.values.get(0));
357 let content_type = value_of(ID_CONTENT_TYPE).and_then(|v| v.decode_as::<ObjectIdentifier>().ok());
358 let message_digest = value_of(ID_MESSAGE_DIGEST).and_then(|v| v.decode_as::<OctetString>().ok());
359 if content_type != Some(ID_DATA)
360 || message_digest.as_ref().map(OctetString::as_bytes) != Some(&digest.digest(signature_file)[..])
361 {
362 return Err(JarError::SignedAttributesMismatch { file: file.to_owned() });
363 }
364 signed_attributes = attributes.to_der().map_err(malformed)?;
365 &signed_attributes
366 }
367 };
368 key.verify_jar(digest, message, signer.signature.as_bytes())
369 .map_err(|source| JarError::BadSignature { file: file.to_owned(), source })?;
370 let mut chain = vec![leaf];
371 for (_, certificate) in certificates.iter().enumerate().filter(|(i, _)| *i != position) {
372 chain.push(to_certificate(certificate)?);
373 }
374 Ok(chain)
375}
376
377fn check_signature_file(
381 signature: &Manifest<'_>,
382 manifest: &Manifest<'_>,
383 manifest_bytes: &[u8],
384 file: &str,
385) -> Result<(), JarError> {
386 let mismatch = || JarError::ManifestDigestMismatch { signature_file: file.to_owned() };
387 if let Some((algorithm, expected)) = signature.main.digest("-Digest-Manifest", file)?
388 && algorithm.digest(manifest_bytes) == expected
389 {
390 return Ok(());
391 }
392 if let Some((algorithm, expected)) = signature.main.digest("-Digest-Manifest-Main-Attributes", file)?
393 && algorithm.digest(manifest.main.raw) != expected
394 {
395 return Err(mismatch());
396 }
397 let mut listed = HashSet::new();
400 for section in &signature.entries {
401 let name = section.name().ok_or_else(mismatch)?;
402 let (algorithm, expected) =
403 section.digest("-Digest", file)?.ok_or_else(|| JarError::UnsupportedDigest { file: file.to_owned() })?;
404 let manifest_section = manifest.entry(name).ok_or_else(mismatch)?;
405 if algorithm.digest(manifest_section.raw) != expected {
406 return Err(mismatch());
407 }
408 listed.insert(name);
409 }
410 match manifest.entries.iter().filter_map(Section::name).find(|name| !listed.contains(name)) {
411 Some(unlisted) => Err(JarError::UnsignedEntry { entry: unlisted.to_owned() }),
412 None => Ok(()),
413 }
414}
415
416type Line = (usize, Vec<u8>);
418
419struct Manifest<'a> {
421 main: Section<'a>,
422 entries: Vec<Section<'a>>,
423 index: HashMap<String, usize>,
424}
425
426struct Section<'a> {
428 raw: &'a [u8],
429 attributes: Vec<(String, String)>,
430}
431
432impl Section<'_> {
433 fn get(&self, name: &str) -> Option<&str> {
434 self.attributes.iter().find(|(n, _)| n.eq_ignore_ascii_case(name)).map(|(_, v)| v.as_str())
435 }
436
437 fn name(&self) -> Option<&str> {
438 self.get("Name")
439 }
440
441 fn digest(&self, suffix: &str, file: &str) -> Result<Option<(DigestAlgorithm, Vec<u8>)>, JarError> {
445 let strongest = self
446 .attributes
447 .iter()
448 .filter_map(|(name, value)| {
449 let (algorithm, tail) = name.split_at_checked(name.len().checked_sub(suffix.len())?)?;
451 tail.eq_ignore_ascii_case(suffix).then_some(())?;
452 Some((DigestAlgorithm::from_jar_name(algorithm)?, value))
453 })
454 .max_by_key(|(algorithm, _)| *algorithm);
455 let Some((algorithm, value)) = strongest else { return Ok(None) };
456 let digest = BASE64.decode(value.trim()).map_err(|_| JarError::InvalidDigest { file: file.to_owned() })?;
457 Ok(Some((algorithm, digest)))
458 }
459}
460
461impl<'a> Manifest<'a> {
462 fn parse(bytes: &'a [u8], file: &str) -> Result<Self, JarError> {
463 let malformed = |line| JarError::MalformedManifest { file: file.to_owned(), line };
464 let mut raw_sections: Vec<(&[u8], Vec<Line>)> = Vec::new();
467 let mut lines: Vec<Line> = Vec::new();
468 let (mut start, mut pos, mut line_number) = (0, 0, 0);
469 while pos < bytes.len() {
470 line_number += 1;
471 let end = bytes[pos..].iter().position(|&b| b == b'\n' || b == b'\r').map_or(bytes.len(), |i| pos + i);
472 let next = match bytes.get(end..end + 2) {
473 Some(b"\r\n") => end + 2,
474 _ => (end + 1).min(bytes.len()),
475 };
476 let line = &bytes[pos..end];
477 if line.is_empty() {
478 if !lines.is_empty() {
479 raw_sections.push((&bytes[start..next], std::mem::take(&mut lines)));
480 }
481 start = next;
482 } else if let Some(continuation) = line.strip_prefix(b" ") {
483 lines.last_mut().ok_or(malformed(line_number))?.1.extend_from_slice(continuation);
484 } else {
485 lines.push((line_number, line.to_vec()));
486 }
487 pos = next;
488 }
489 if !lines.is_empty() {
490 raw_sections.push((&bytes[start..], lines));
491 }
492 let mut sections = Vec::with_capacity(raw_sections.len());
493 for (raw, lines) in raw_sections {
494 let attributes = lines
495 .into_iter()
496 .map(|(number, line)| {
497 let text = String::from_utf8(line).map_err(|_| malformed(number))?;
498 let (name, value) = text.split_once(": ").ok_or(malformed(number))?;
499 Ok((name.to_owned(), value.to_owned()))
500 })
501 .collect::<Result<_, JarError>>()?;
502 sections.push(Section { raw, attributes });
503 }
504 let mut sections = sections.into_iter();
505 let main = sections.next().unwrap_or(Section { raw: &[], attributes: Vec::new() });
506 let entries: Vec<Section<'a>> = sections.collect();
507 let mut index = HashMap::new();
508 for (i, section) in entries.iter().enumerate() {
509 let name = section.name().ok_or(malformed(0))?;
510 if index.insert(name.to_owned(), i).is_some() {
511 return Err(JarError::DuplicateSection { file: file.to_owned(), name: name.to_owned() });
512 }
513 }
514 Ok(Self { main, entries, index })
515 }
516
517 fn entry(&self, name: &str) -> Option<&Section<'a>> {
518 self.index.get(name).map(|&i| &self.entries[i])
519 }
520}
521
522#[cfg(test)]
523mod tests {
524 use super::*;
525 use crate::archive::is_signature_file;
526
527 #[test]
528 fn folds_lines_at_72_bytes() {
529 let name = format!("assets/{}雪.bin", "x".repeat(80));
530 let bytes = section(&[("Name", &name)]);
531 assert!(bytes.split(|&b| b == b'\n').all(|line| line.len() <= MAX_LINE + 1));
532 let manifest = Manifest::parse(&bytes, "test").unwrap();
533 assert_eq!(manifest.main.get("Name"), Some(name.as_str()));
534 }
535
536 #[test]
537 fn classifies_signature_files_like_apksigner() {
538 for name in ["META-INF/MANIFEST.MF", "META-INF/CERT.SF", "META-INF/cert.rsa", "META-INF/A.EC", "META-INF/SIG-X"]
539 {
540 assert!(is_signature_file(name), "{name}");
541 assert!(!is_signed_entry(name), "{name}");
542 }
543 for name in ["META-INF/services/x", "META-INF/LICENSE", "classes.dex", "meta-inf/CERT.SF"] {
544 assert!(!is_signature_file(name), "{name}");
545 assert!(is_signed_entry(name), "{name}");
546 }
547 assert!(!is_signed_entry("assets/"));
548 }
549
550 #[test]
551 fn multibyte_attribute_names_do_not_panic() {
552 let manifest = Manifest::parse("€€€: x\r\n€€€€€€: y\r\n\r\n".as_bytes(), "m").unwrap();
554 assert_eq!(manifest.main.digest("-Digest", "m").unwrap(), None);
555 assert_eq!(manifest.main.digest("-Digest-Manifest", "m").unwrap(), None);
556 }
557
558 #[test]
559 fn the_strongest_digest_counts() {
560 let text = b"Name: a\r\nSHA-256-Digest: AAAA\r\nSHA-512-Digest: AQID\r\n\r\n";
561 let manifest = Manifest::parse(text, "m").unwrap();
562 assert_eq!(manifest.main.digest("-Digest", "m").unwrap(), Some((DigestAlgorithm::Sha512, vec![1, 2, 3])));
563 let manifest = Manifest::parse(b"Name: a\r\nSHA-256-Digest: AAAA\r\nSHA-512-Digest: !!\r\n\r\n", "m").unwrap();
565 assert!(matches!(manifest.main.digest("-Digest", "m"), Err(JarError::InvalidDigest { .. })));
566 }
567
568 fn signed_archive(listed: &[&str]) -> Vec<u8> {
571 let identity = SigningIdentity::from_pem(include_str!("../../tests/fixtures/keys/debug.pem")).unwrap();
572 let entries = [("a", b"alpha".as_slice()), ("b", b"beta".as_slice())];
573 let digests: Vec<(String, Vec<u8>)> =
574 entries.iter().map(|(name, data)| (name.to_string(), DigestAlgorithm::Sha256.digest(data))).collect();
575 let manifest_bytes =
576 sign(&digests, DigestAlgorithm::Sha256, std::slice::from_ref(&identity), &[]).unwrap()[0].1.clone();
577 let manifest = Manifest::parse(&manifest_bytes, MANIFEST).unwrap();
578 let whole = BASE64.encode(DigestAlgorithm::Sha256.digest(&manifest_bytes));
579 let mut signature_file = section(&[("Signature-Version", "1.0"), ("SHA-256-Digest-Manifest", &whole)]);
580 for name in listed {
581 let digest = BASE64.encode(DigestAlgorithm::Sha256.digest(manifest.entry(name).unwrap().raw));
582 signature_file.extend(section(&[("Name", name), ("SHA-256-Digest", &digest)]));
583 }
584 let block = signature_block(&signature_file, &identity, DigestAlgorithm::Sha256).unwrap();
585 let mut zip = zip::ZipWriter::new(std::io::Cursor::new(Vec::new()));
586 let files = entries.iter().map(|&(name, data)| (name, data)).chain([
587 (MANIFEST, manifest_bytes.as_slice()),
588 ("META-INF/CERT.SF", signature_file.as_slice()),
589 ("META-INF/CERT.RSA", block.as_slice()),
590 ]);
591 for (name, data) in files {
592 zip.start_file(name, zip::write::SimpleFileOptions::default()).unwrap();
593 std::io::Write::write_all(&mut zip, data).unwrap();
594 }
595 zip.finish().unwrap().into_inner()
596 }
597
598 #[test]
599 fn every_signed_entry_must_be_named_by_every_signer() {
600 let verify_listing = |listed: &[&str]| {
601 let bytes = signed_archive(listed);
602 verify(&mut ZipArchive::new(std::io::Cursor::new(bytes)).unwrap())
603 };
604 assert_eq!(verify_listing(&["a", "b"]).unwrap().unwrap().signers.len(), 1);
605 assert!(matches!(
606 verify_listing(&["a"]),
607 Err(VerifyError::Jar(JarError::UnsignedEntry { entry })) if entry == "b"
608 ));
609 }
610
611 #[test]
612 fn per_section_signature_files_must_cover_every_manifest_section() {
613 let manifest_bytes = b"Manifest-Version: 1.0\r\n\r\nName: a\r\nSHA-256-Digest: AA==\r\n\r\nName: b\r\nSHA-256-Digest: AA==\r\n\r\n";
614 let manifest = Manifest::parse(manifest_bytes, MANIFEST).unwrap();
615 let section_a = BASE64.encode(DigestAlgorithm::Sha256.digest(manifest.entry("a").unwrap().raw));
616 let signature = format!("Signature-Version: 1.0\r\n\r\nName: a\r\nSHA-256-Digest: {section_a}\r\n\r\n");
617 let signature = Manifest::parse(signature.as_bytes(), "CERT.SF").unwrap();
618 assert!(matches!(
619 check_signature_file(&signature, &manifest, manifest_bytes, "CERT.SF"),
620 Err(JarError::UnsignedEntry { entry }) if entry == "b"
621 ));
622 }
623
624 #[test]
625 fn parses_lf_and_crlf_manifests() {
626 let text = b"Manifest-Version: 1.0\n\nName: a\nSHA-256-Digest: AAAA\n\nName: b\r\nSHA1-Digest: AAAA\r\n";
627 let manifest = Manifest::parse(text, "m").unwrap();
628 assert_eq!(manifest.entries.len(), 2);
629 let algorithm = |name| manifest.entry(name).unwrap().digest("-Digest", "m").unwrap().unwrap().0;
630 assert_eq!((algorithm("a"), algorithm("b")), (DigestAlgorithm::Sha256, DigestAlgorithm::Sha1));
631 assert!(matches!(
632 Manifest::parse(b"A: 1\n\nName: x\n\nName: x\n", "m"),
633 Err(JarError::DuplicateSection { .. })
634 ));
635 }
636
637 fn block_claiming(key: &str, algorithm: ObjectIdentifier, signature_file: &[u8]) -> Vec<u8> {
640 let pem = match key {
641 "rsa" => include_str!("../../tests/fixtures/keys/debug.pem"),
642 _ => include_str!("../../tests/fixtures/keys/ec-p256.pem"),
643 };
644 let identity = SigningIdentity::from_pem(pem).unwrap();
645 let block = signature_block(signature_file, &identity, DigestAlgorithm::Sha256).unwrap();
646 let info = ContentInfo::from_der(&block).unwrap();
647 let mut signed: SignedData = info.content.decode_as().unwrap();
648 let mut signer = signed.signer_infos.0.as_slice()[0].clone();
649 signer.signature_algorithm.oid = algorithm;
650 signed.signer_infos = SignerInfos(SetOfVec::try_from(vec![signer]).unwrap());
651 let content = Any::encode_from(&signed).unwrap();
652 ContentInfo { content_type: ID_SIGNED_DATA, content }.to_der().unwrap()
653 }
654
655 #[test]
656 fn signature_algorithms_must_fit_the_key_and_digest() {
657 let file = b"Signature-Version: 1.0\r\n\r\n";
658 for (key, algorithm) in
659 [("rsa", RSA_ENCRYPTION), ("rsa", SHA256_WITH_RSA), ("ec", ECDSA_WITH_SHA256), ("ec", EC_PUBLIC_KEY)]
660 {
661 verify_block(&block_claiming(key, algorithm, file), file, "CERT")
662 .unwrap_or_else(|e| panic!("{algorithm}: {e}"));
663 }
664 for (key, algorithm) in
665 [("ec", RSA_ENCRYPTION), ("rsa", ECDSA_WITH_SHA256), ("rsa", SHA1_WITH_RSA), ("ec", ECDSA_WITH_SHA1)]
666 {
667 assert!(
668 matches!(
669 verify_block(&block_claiming(key, algorithm, file), file, "CERT"),
670 Err(JarError::SignatureAlgorithmMismatch { .. })
671 ),
672 "{key} key accepted {algorithm}"
673 );
674 }
675 }
676}