rust-apk 1.0.0

Build, align, sign, verify, and inspect Android APKs in pure Rust
Documentation
//! APK Signature Schemes v2 and v3, which share one record layout; v3 adds
//! an SDK range to each signer and its signed data.

use binrw::binrw;

use super::block::{Blob, Prefixed, Seq, read_exact, read_leading, write_vec};
use super::{Certificate, PublicKey, Scheme, SignError, SignatureAlgorithm, SigningIdentity, VerifyError};
use crate::digest::ContentDigest;

/// The first API level that verifies v3 signatures, where v3 signers' SDK
/// ranges start.
pub(crate) const V3_MIN_SDK: u32 = 28;
/// The largest API level (Android stores levels as `int`).
pub(crate) const MAX_SDK: u32 = i32::MAX as u32;

/// v2 signed-data attribute listing stronger schemes that must also be
/// present, so that stripping them is detected.
const STRIPPING_PROTECTION: u32 = 0xbeef_f00d;
/// v3 signed-data attributes of key rotation: a lineage, the SDK from which
/// the rotated key (in a v3.1 block) applies, and rotation that targets a
/// development release.
const ROTATION_ATTRIBUTES: [u32; 3] = [PROOF_OF_ROTATION, 0x559f_8b02, 0xc2a6_b3ba];
const PROOF_OF_ROTATION: u32 = 0x3ba0_6f8c;

#[binrw]
#[brw(little)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct SdkRange {
    pub min: u32,
    pub max: u32,
}

#[binrw]
#[brw(little, import(v3: bool))]
#[derive(Clone, Debug, Eq, PartialEq)]
struct SignerRecord {
    signed_data: Prefixed<Blob>,
    #[brw(if(v3))]
    sdk: Option<SdkRange>,
    signatures: Prefixed<Seq<Prefixed<SignatureRecord>>>,
    public_key: Prefixed<Blob>,
}

#[binrw]
#[brw(little, import(v3: bool))]
#[derive(Clone, Debug, Eq, PartialEq)]
struct SignedData {
    digests: Prefixed<Seq<Prefixed<AlgorithmValue>>>,
    certificates: Prefixed<Seq<Prefixed<Blob>>>,
    #[brw(if(v3))]
    sdk: Option<SdkRange>,
    attributes: Prefixed<Seq<Prefixed<Attribute>>>,
}

/// A signature or digest record: an algorithm ID and its value.
#[binrw]
#[brw(little)]
#[derive(Clone, Debug, Eq, PartialEq)]
struct AlgorithmValue {
    algorithm: u32,
    value: Prefixed<Blob>,
}

type SignatureRecord = AlgorithmValue;

#[binrw]
#[brw(little)]
#[derive(Clone, Debug, Eq, PartialEq)]
struct Attribute {
    id: u32,
    value: Blob,
}

type Signers = Prefixed<Seq<Prefixed<SignerRecord>>>;

/// Encodes the `scheme` block value signed by `identities`. `digests` holds
/// the content digests the identities' algorithms need; `stronger` lists
/// schemes also applied, recorded for stripping protection.
pub(crate) fn sign(
    scheme: Scheme,
    identities: &[SigningIdentity],
    digests: &[(ContentDigest, Vec<u8>)],
    stronger: &[Scheme],
) -> Result<Vec<u8>, SignError> {
    let v3 = scheme == Scheme::V3;
    let sdk = v3.then_some(SdkRange { min: V3_MIN_SDK, max: MAX_SDK });
    let attributes = stronger
        .iter()
        .filter(|_| scheme == Scheme::V2)
        .map(|s| Prefixed(Attribute { id: STRIPPING_PROTECTION, value: Blob(s.number().to_le_bytes().to_vec()) }))
        .collect();
    let attributes = Prefixed(Seq(attributes));
    let signers = identities
        .iter()
        .map(|identity| {
            let content = identity.key().signature_algorithm().content_digest();
            let content = content.expect("signing algorithms have computable digests");
            let digest = digests.iter().find(|(d, _)| *d == content).map(|(_, v)| v.clone());
            let digest = digest.expect("digest computed per signer");
            signer_record(identity, &signed_data(identity, digest, sdk, attributes.clone()), v3)
        })
        .collect::<Result<_, _>>()?;
    Ok(write_vec::<Signers>(&Prefixed(Seq(signers)), (v3,)))
}

/// The signed data of `identity`'s signature over `digest`.
fn signed_data(
    identity: &SigningIdentity,
    digest: Vec<u8>,
    sdk: Option<SdkRange>,
    attributes: Prefixed<Seq<Prefixed<Attribute>>>,
) -> SignedData {
    let algorithm = identity.key().signature_algorithm();
    SignedData {
        digests: Prefixed(Seq(vec![Prefixed(AlgorithmValue {
            algorithm: algorithm.id(),
            value: Prefixed(Blob(digest)),
        })])),
        certificates: Prefixed(Seq(identity.chain().iter().map(|c| Prefixed(Blob(c.der().to_vec()))).collect())),
        sdk,
        attributes,
    }
}

/// A signer record carrying `signed`, signed by `identity`.
fn signer_record(
    identity: &SigningIdentity,
    signed: &SignedData,
    v3: bool,
) -> Result<Prefixed<SignerRecord>, SignError> {
    let algorithm = identity.key().signature_algorithm();
    let signed_bytes = write_vec(signed, (v3,));
    let signature = identity.key().sign(algorithm, &signed_bytes)?;
    Ok(Prefixed(SignerRecord {
        signed_data: Prefixed(Blob(signed_bytes)),
        sdk: signed.sdk,
        signatures: Prefixed(Seq(vec![Prefixed(AlgorithmValue {
            algorithm: algorithm.id(),
            value: Prefixed(Blob(signature)),
        })])),
        public_key: Prefixed(Blob(identity.certificate().public_key_der()?)),
    }))
}

/// A signer whose signature verified.
#[derive(Clone, Debug)]
pub(crate) struct SchemeSigner {
    pub certificates: Vec<Certificate>,
    pub algorithm: SignatureAlgorithm,
    pub sdk: Option<SdkRange>,
    /// Schemes this signer's signed data declares must also be present.
    pub protected_schemes: Vec<u32>,
}

/// Verifies every signer in a `scheme` block value. `content_digest`
/// computes (or recalls) the APK's digest of a kind; returning `None` skips
/// the content check, which signing uses to self-check fresh records.
pub(crate) fn verify(
    scheme: Scheme,
    value: &[u8],
    content_digest: &mut dyn FnMut(ContentDigest) -> Result<Option<Vec<u8>>, VerifyError>,
) -> Result<Vec<SchemeSigner>, VerifyError> {
    let v3 = scheme == Scheme::V3;
    let malformed = |offset| VerifyError::MalformedScheme { scheme, offset };
    let signers: Signers = read_exact(value, (v3,)).map_err(malformed)?;
    if signers.0.0.is_empty() {
        return Err(VerifyError::NoSigners { scheme });
    }
    signers
        .0
        .0
        .into_iter()
        .enumerate()
        .map(|(index, Prefixed(signer))| verify_signer(scheme, index, signer, content_digest))
        .collect()
}

fn verify_signer(
    scheme: Scheme,
    signer: usize,
    record: SignerRecord,
    content_digest: &mut dyn FnMut(ContentDigest) -> Result<Option<Vec<u8>>, VerifyError>,
) -> Result<SchemeSigner, VerifyError> {
    let v3 = scheme == Scheme::V3;
    let signatures: Vec<AlgorithmValue> = record.signatures.0.0.into_iter().map(|p| p.0).collect();
    if signatures.is_empty() {
        return Err(VerifyError::NoSignatures { scheme, signer });
    }
    // Like the platform: the strongest known algorithm (the first of equals)
    // must verify. A weaker one is never tried instead.
    let mut best: Option<(SignatureAlgorithm, &AlgorithmValue)> = None;
    for candidate in &signatures {
        if let Some(algorithm) = SignatureAlgorithm::from_id(candidate.algorithm)
            && best.is_none_or(|(best, _)| algorithm.strength() > best.strength())
        {
            best = Some((algorithm, candidate));
        }
    }
    let (algorithm, signature) = best.ok_or_else(|| VerifyError::NoSupportedAlgorithm {
        scheme,
        signer,
        ids: signatures.iter().map(|s| s.algorithm).collect(),
    })?;

    // Authenticate the signed data before parsing anything inside it.
    let public_key_der = record.public_key.0.0;
    let public_key = PublicKey::from_spki_der(&public_key_der)?;
    let signed_bytes = record.signed_data.0.0;
    public_key.verify(algorithm, &signed_bytes, &signature.value.0.0).map_err(|source| VerifyError::BadSignature {
        scheme,
        signer,
        algorithm,
        source,
    })?;

    // apksigner ends signed data with an empty field, which Android ignores.
    let signed: SignedData =
        read_leading(&signed_bytes, (v3,)).map_err(|offset| VerifyError::MalformedScheme { scheme, offset })?;
    let digests: Vec<AlgorithmValue> = signed.digests.0.0.into_iter().map(|p| p.0).collect();
    let signature_ids: Vec<u32> = signatures.iter().map(|s| s.algorithm).collect();
    let digest_ids: Vec<u32> = digests.iter().map(|d| d.algorithm).collect();
    if signature_ids != digest_ids {
        return Err(VerifyError::AlgorithmListMismatch { scheme, signer });
    }
    let certificates =
        signed.certificates.0.0.iter().map(|c| Certificate::from_der(&c.0.0)).collect::<Result<Vec<_>, _>>()?;
    let leaf = certificates.first().ok_or(VerifyError::NoCertificates { scheme, signer })?;
    if leaf.public_key_der()? != public_key_der {
        return Err(VerifyError::PublicKeyMismatch { scheme, signer });
    }
    if v3 && record.sdk != signed.sdk {
        return Err(VerifyError::SdkRangeMismatch { scheme, signer });
    }
    if let Some(SdkRange { min, max }) = signed.sdk
        && (min > max || max > MAX_SDK)
    {
        return Err(VerifyError::InvalidSdkRange { scheme, signer, min, max });
    }
    // Rotation can only be trusted once its whole lineage is verified, which
    // this crate does not do; it is never ignored.
    if signed.attributes.0.0.iter().any(|a| ROTATION_ATTRIBUTES.contains(&a.0.id)) {
        return Err(VerifyError::KeyRotationUnsupported);
    }
    let expected = digests
        .iter()
        .find(|d| d.algorithm == algorithm.id())
        .map(|d| &d.value.0.0)
        .ok_or(VerifyError::MissingDigest { scheme, signer, algorithm })?;
    let digest = algorithm.content_digest().expect("verifiable algorithms have computable digests");
    if let Some(actual) = content_digest(digest)?
        && actual != *expected
    {
        return Err(VerifyError::DigestMismatch { scheme, signer });
    }
    let protected_schemes = signed
        .attributes
        .0
        .0
        .iter()
        .filter(|a| a.0.id == STRIPPING_PROTECTION)
        .filter_map(|a| a.0.value.0.get(..4).map(|b| u32::from_le_bytes([b[0], b[1], b[2], b[3]])))
        .collect();
    Ok(SchemeSigner { certificates, algorithm, sdk: signed.sdk, protected_schemes })
}

#[cfg(test)]
mod tests {
    use super::*;

    fn identity() -> SigningIdentity {
        SigningIdentity::from_pem(include_str!("../../tests/fixtures/keys/debug.pem")).unwrap()
    }

    /// A v3 block whose one signer validly signs `sdk` and `attributes`.
    fn v3_block(sdk: SdkRange, attributes: Vec<Attribute>) -> Vec<u8> {
        let identity = identity();
        let attributes = Prefixed(Seq(attributes.into_iter().map(Prefixed).collect()));
        let signed = signed_data(&identity, vec![0; 32], Some(sdk), attributes);
        let record = signer_record(&identity, &signed, true).unwrap();
        write_vec::<Signers>(&Prefixed(Seq(vec![record])), (true,))
    }

    fn verify_v3(block: &[u8]) -> Result<Vec<SchemeSigner>, VerifyError> {
        verify(Scheme::V3, block, &mut |_| Ok(None))
    }

    #[test]
    fn well_formed_signers_verify() {
        let signers = verify_v3(&v3_block(SdkRange { min: V3_MIN_SDK, max: MAX_SDK }, Vec::new())).unwrap();
        assert_eq!(signers[0].certificates[0], *identity().certificate());
    }

    #[test]
    fn rotation_proofs_are_never_ignored() {
        let proof = Attribute { id: PROOF_OF_ROTATION, value: Blob(b"not a lineage".to_vec()) };
        let block = v3_block(SdkRange { min: V3_MIN_SDK, max: MAX_SDK }, vec![proof]);
        assert!(matches!(verify_v3(&block), Err(VerifyError::KeyRotationUnsupported)));
    }

    #[test]
    fn empty_sdk_ranges_are_rejected() {
        let block = v3_block(SdkRange { min: 40, max: 28 }, Vec::new());
        assert!(matches!(verify_v3(&block), Err(VerifyError::InvalidSdkRange { min: 40, max: 28, .. })));
    }
}