Skip to main content

apk/sign/
jar.rs

1//! JAR signing (APK Signature Scheme v1): `META-INF/MANIFEST.MF`, a
2//! signature file (`.SF`) per signer, and a CMS signature block.
3
4use 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;
28/// The largest JAR signature file (`MANIFEST.MF`, `.SF`, or block) read.
29const 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");
45/// Signature algorithms with the digest they name, if any, by key family.
46const 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/// Why a JAR signature could not be read or did not verify.
62#[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
99/// Builds `MANIFEST.MF` and, per identity, a signature file and block, from
100/// the name and digest of every signed entry. `declared` lists the stronger
101/// schemes also applied, for stripping protection.
102pub(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 &sections {
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
147/// A manifest section: attribute lines folded at 72 bytes, then a blank line.
148fn 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
170/// The signature algorithm identifier apksigner writes for a key and digest.
171fn 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
183/// Whether a signer may name `algorithm` for a signature by `key` over a
184/// `digest`: the algorithm must belong to the key's family, and any digest
185/// it names must be `digest`. This is the set apksigner accepts.
186fn 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
194/// A detached CMS `SignedData` over `signature_file`, without signed
195/// attributes, as apksigner writes.
196fn 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
233/// A verified JAR signature.
234pub(crate) struct JarSignature {
235    /// Each signer's certificate chain, signer first.
236    pub signers: Vec<Vec<Certificate>>,
237    /// Schemes the signature files declare (`X-Android-APK-Signed`).
238    pub declared_schemes: Vec<u32>,
239}
240
241/// Whether `name` is a signature block (`META-INF/*.RSA`, `*.DSA`, `*.EC`).
242fn 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
248/// Verifies the JAR signature of `archive`, or returns `None` if it has none.
249///
250/// Like apksigner, every signature block is a signer, whose signature file
251/// shares its name, and every signed entry must be covered by every signer.
252pub(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        // Android credits a signer only with the entries its signature file names.
282        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
307/// Verifies a CMS signature block over `signature_file` and returns the
308/// signer's certificate chain, signer first.
309fn 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    // Android accepts BER, which older signing tools produced.
313    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
377/// Checks a signature file against the manifest, as Android does: by its
378/// whole-manifest digest, or else by the digests of the manifest's main
379/// attributes (when given) and of every manifest section.
380fn 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    // Without a whole-manifest digest, every manifest section must be
398    // authenticated individually; an unlisted section could be edited freely.
399    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
416/// A logical manifest line, continuations joined, and its first line number.
417type Line = (usize, Vec<u8>);
418
419/// A parsed manifest or signature file.
420struct Manifest<'a> {
421    main: Section<'a>,
422    entries: Vec<Section<'a>>,
423    index: HashMap<String, usize>,
424}
425
426/// One section: its exact bytes (for digests) and attributes.
427struct 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    /// The strongest `<ALG><suffix>` digest attribute, which, like Android,
442    /// is the one that counts: a malformed value is an error, not a reason
443    /// to fall back to a weaker digest. `file` names the file in errors.
444    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                // `split_at_checked` rejects splits inside a multi-byte character.
450                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        // Lines are folded at a byte count, possibly inside a UTF-8 sequence,
465        // so continuations are joined before anything is decoded.
466        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        // "€€€" is 9 bytes; stripping a 7-byte suffix would split a character.
553        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        // A malformed strongest digest is an error, not a reason to use a weaker one.
564        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    /// An archive with entries `a` and `b`, JAR-signed by the debug key with
569    /// a signature file that has a whole-manifest digest and lists `listed`.
570    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    /// A signature block by `key` over `signature_file`, with its signature
638    /// algorithm identifier replaced by `algorithm`.
639    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}