Skip to main content

apk/sign/
verify.rs

1//! Verifying all of an APK's signatures.
2
3use std::collections::BTreeSet;
4use std::fs::File;
5use std::io::{Cursor, Read, Seek};
6use std::ops::RangeInclusive;
7use std::path::Path;
8
9use super::scheme::{MAX_SDK, SchemeSigner, V3_MIN_SDK};
10use super::{BlockId, Certificate, Scheme, Schemes, SignatureAlgorithm, SigningBlock, VerifyError, jar, scheme};
11use crate::archive::{self, ReadAt, ZipError};
12use crate::digest::{ContentDigest, content_digests};
13use crate::error::{Error, FileOperation};
14
15/// The outcome of a successful verification.
16#[derive(Clone, Debug)]
17pub struct Verification {
18    /// The schemes whose signatures are present and valid.
19    pub schemes: Schemes,
20    signers: Vec<VerifiedSigner>,
21}
22
23impl Verification {
24    /// The first signer's leaf certificate.
25    pub fn certificate(&self) -> &Certificate {
26        self.signers[0].certificate()
27    }
28
29    /// Signers from the strongest scheme present. Always nonempty.
30    pub fn signers(&self) -> &[VerifiedSigner] {
31        &self.signers
32    }
33}
34
35/// A signer whose signature verified.
36#[derive(Clone, Debug)]
37pub struct VerifiedSigner {
38    certificates: Vec<Certificate>,
39    /// The algorithm of a v2 or v3 signature.
40    pub algorithm: Option<SignatureAlgorithm>,
41    /// The API levels a v3 signer applies to.
42    pub sdk_range: Option<RangeInclusive<u32>>,
43}
44
45impl VerifiedSigner {
46    /// The leaf certificate that verified this signer's signature.
47    pub fn certificate(&self) -> &Certificate {
48        &self.certificates[0]
49    }
50
51    /// The embedded certificate chain, leaf first. Always nonempty.
52    pub fn certificates(&self) -> &[Certificate] {
53        &self.certificates
54    }
55}
56
57impl From<SchemeSigner> for VerifiedSigner {
58    fn from(signer: SchemeSigner) -> Self {
59        Self {
60            certificates: signer.certificates,
61            algorithm: Some(signer.algorithm),
62            sdk_range: signer.sdk.map(|sdk| sdk.min..=sdk.max),
63        }
64    }
65}
66
67/// Verifies APK signatures, content digests, and SDK scheme coverage.
68///
69/// Every scheme present must verify and name the same signers, any scheme
70/// another declares must be present (stripping protection), and the
71/// schemes must cover every release from the manifest's `minSdkVersion`.
72/// This establishes integrity and who signed the APK, not whether that
73/// signer is trusted. APKs that rotate their signing key are rejected with
74/// [`VerifyError::KeyRotationUnsupported`].
75pub fn verify(path: impl AsRef<Path>) -> Result<Verification, Error> {
76    let path = path.as_ref();
77    let file = File::open(path).map_err(Error::file(FileOperation::Open, path))?;
78    verify_source(&file, &file)
79}
80
81/// Verifies an APK held in memory, like [`verify`].
82pub fn verify_bytes(bytes: &[u8]) -> Result<Verification, Error> {
83    verify_source(bytes, Cursor::new(bytes))
84}
85
86fn verify_source(source: &(impl ReadAt + ?Sized), reader: impl Read + Seek) -> Result<Verification, Error> {
87    let (mut archive, layout) = archive::open(reader)?;
88    let manifest = crate::apk::read_manifest(&mut archive)?;
89    let first_v3_sdk = manifest
90        .as_ref()
91        .map(|manifest| manifest.min_sdk().api_level())
92        .transpose()?
93        .map_or(V3_MIN_SDK, |sdk| sdk.get().max(V3_MIN_SDK));
94
95    let mut digests: Vec<(ContentDigest, Vec<u8>)> = Vec::new();
96    let mut content_digest = |kind: ContentDigest| -> Result<Option<Vec<u8>>, VerifyError> {
97        if let Some((_, digest)) = digests.iter().find(|(k, _)| *k == kind) {
98            return Ok(Some(digest.clone()));
99        }
100        let (_, digest) = content_digests(source, &layout, &[kind])?.remove(0);
101        digests.push((kind, digest.clone()));
102        Ok(Some(digest))
103    };
104    let block = match &layout.signing_block {
105        Some(range) => {
106            let bytes = source.read_vec_at(range.start, (range.end - range.start) as usize).map_err(ZipError::from)?;
107            SigningBlock::parse(&bytes)?
108        }
109        None => SigningBlock::default(),
110    };
111    if block.get(BlockId::V3_1).is_some() {
112        return Err(VerifyError::KeyRotationUnsupported.into());
113    }
114
115    let mut verified: Vec<(Scheme, Vec<VerifiedSigner>)> = Vec::new();
116    let mut declared: Vec<(Scheme, u32)> = Vec::new();
117    for scheme in [Scheme::V3, Scheme::V2] {
118        let Some(value) = block.get(scheme.block_id().expect("block scheme")) else { continue };
119        let signers = scheme::verify(scheme, value, &mut content_digest)?;
120        if scheme == Scheme::V3 {
121            check_v3_coverage(&signers, first_v3_sdk)?;
122        }
123        declared.extend(signers.iter().flat_map(|s| s.protected_schemes.iter().map(move |&p| (scheme, p))));
124        verified.push((scheme, signers.into_iter().map(VerifiedSigner::from).collect()));
125    }
126    if let Some(signature) = jar::verify(&mut archive)? {
127        declared.extend(signature.declared_schemes.iter().map(|&p| (Scheme::V1, p)));
128        let signers = signature
129            .signers
130            .into_iter()
131            .map(|certificates| VerifiedSigner { certificates, algorithm: None, sdk_range: None })
132            .collect();
133        verified.push((Scheme::V1, signers));
134    }
135    let Some((strongest, _)) = verified.first() else { return Err(VerifyError::Unsigned.into()) };
136
137    let schemes = verified.iter().fold(Schemes::empty(), |set, (scheme, _)| set | scheme.flag());
138    for (protected_by, number) in declared {
139        match Scheme::from_number(number) {
140            Some(scheme) if !schemes.contains(scheme.flag()) => {
141                return Err(VerifyError::StrippedScheme { scheme, protected_by }.into());
142            }
143            _ => {}
144        }
145    }
146    let leaves =
147        |signers: &[VerifiedSigner]| signers.iter().map(|s| s.certificates[0].der().to_vec()).collect::<BTreeSet<_>>();
148    let expected = leaves(&verified[0].1);
149    if let Some((other, _)) = verified.iter().skip(1).find(|(_, signers)| leaves(signers) != expected) {
150        return Err(VerifyError::SignerMismatch { first: *strongest, second: *other }.into());
151    }
152    if let Some(manifest) = &manifest {
153        let (min_sdk, target_sdk) = (manifest.min_sdk().api_level()?, manifest.effective_target_sdk()?);
154        schemes.check_coverage(min_sdk, target_sdk).map_err(VerifyError::Coverage)?;
155    }
156    let (_, signers) = verified.swap_remove(0);
157    Ok(Verification { schemes, signers })
158}
159
160/// Covers the app's supported v3 releases exactly once.
161fn check_v3_coverage(signers: &[SchemeSigner], first_sdk: u32) -> Result<(), VerifyError> {
162    let gap = |sdk| VerifyError::SdkCoverage { scheme: Scheme::V3, sdk };
163    let mut ranges: Vec<_> = signers.iter().filter_map(|s| s.sdk).filter(|r| r.max >= first_sdk).collect();
164    ranges.sort_by_key(|r| r.min);
165    let mut next = first_sdk;
166    for range in ranges {
167        let start = range.min.max(first_sdk);
168        if start != next {
169            return Err(gap(start.min(next)));
170        }
171        next = range.max + 1;
172    }
173    if next != MAX_SDK + 1 {
174        return Err(gap(next));
175    }
176    Ok(())
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182    use crate::sign::scheme::SdkRange;
183
184    fn signers(ranges: &[(u32, u32)]) -> Vec<SchemeSigner> {
185        ranges
186            .iter()
187            .map(|&(min, max)| SchemeSigner {
188                certificates: Vec::new(),
189                algorithm: SignatureAlgorithm::EcdsaSha256,
190                sdk: Some(SdkRange { min, max }),
191                protected_schemes: Vec::new(),
192            })
193            .collect()
194    }
195
196    #[test]
197    fn v3_coverage_starts_at_the_apps_minimum_sdk() {
198        let ranges = signers(&[(35, MAX_SDK)]);
199        check_v3_coverage(&ranges, 35).unwrap();
200        assert!(matches!(check_v3_coverage(&ranges, 28), Err(VerifyError::SdkCoverage { sdk: 28, .. })));
201        check_v3_coverage(&signers(&[(28, MAX_SDK)]), 35).unwrap();
202    }
203
204    #[test]
205    fn v3_coverage_rejects_gaps_and_overlaps() {
206        for (ranges, sdk) in
207            [(vec![(28, 39), (41, MAX_SDK)], 40), (vec![(28, 40), (40, MAX_SDK)], 40), (vec![(28, 40)], 41)]
208        {
209            assert!(matches!(
210                check_v3_coverage(&signers(&ranges), 28),
211                Err(VerifyError::SdkCoverage { sdk: found, .. }) if found == sdk
212            ));
213        }
214    }
215}