rust-apk 1.0.0

Build, align, sign, verify, and inspect Android APKs in pure Rust
Documentation
//! The APK signing block and the length-prefixed records inside it.
//!
//! ```text
//! u64  size of the block, excluding this field
//! repeated: u64 length, u32 ID, value
//! u64  size of the block (again)
//! "APK Sig Block 42"
//! ```
//!
//! Signature scheme blocks nest `u32`-length-prefixed records. [`Prefixed`],
//! [`Seq`], and [`Blob`] express that nesting declaratively for `binrw`,
//! with every length bounded by its enclosing record.

use std::fmt;
use std::io::{Cursor, Read, Seek, SeekFrom, Write};

use binrw::io::TakeSeekExt;
use binrw::{BinRead, BinResult, BinWrite, Endian};

use super::VerifyError;
use crate::archive::SIGNING_BLOCK_MAGIC;

/// The ID of an APK signing block entry.
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct BlockId(pub u32);

impl BlockId {
    /// APK Signature Scheme v2.
    pub const V2: Self = Self(0x7109_871a);
    /// APK Signature Scheme v3.
    pub const V3: Self = Self(0xf053_68c0);
    /// APK Signature Scheme v3.1 (key rotation targeting newer platforms).
    pub const V3_1: Self = Self(0x1b93_ad61);
    /// Padding that aligns the block for fs-verity.
    pub const VERITY_PADDING: Self = Self(0x4272_6577);
    /// Google Play dependency metadata.
    pub const DEPENDENCY_INFO: Self = Self(0x504b_4453);
    /// APK source stamp (v2).
    pub const SOURCE_STAMP: Self = Self(0x6dff_800d);
    /// APK source stamp (v1).
    pub const SOURCE_STAMP_V1: Self = Self(0x2b09_189e);

    /// Whether this entry holds signatures or padding tied to the current
    /// contents, which re-signing must replace rather than keep.
    pub fn is_signature_data(self) -> bool {
        [Self::V2, Self::V3, Self::V3_1, Self::VERITY_PADDING, Self::SOURCE_STAMP, Self::SOURCE_STAMP_V1]
            .contains(&self)
    }
}

impl fmt::Debug for BlockId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "BlockId({:#010x})", self.0)
    }
}

/// The ID-value pairs of an APK signing block.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct SigningBlock {
    pub pairs: Vec<(BlockId, Vec<u8>)>,
}

/// Fixed bytes around the pairs: leading size, trailing size, and magic.
const FRAME: usize = 8 + 8 + 16;

impl SigningBlock {
    /// Parses a complete signing block, as located by
    /// [`ApkLayout::signing_block`](crate::archive::ApkLayout::signing_block).
    /// Both size fields must equal the block's length after the first.
    pub fn parse(block: &[u8]) -> Result<Self, VerifyError> {
        let len = block.len();
        if len < FRAME || block[len - 16..] != SIGNING_BLOCK_MAGIC {
            return Err(VerifyError::MalformedBlock);
        }
        let size = |at: usize| u64::from_le_bytes(block[at..at + 8].try_into().expect("eight bytes"));
        if size(0) != (len - 8) as u64 || size(len - 24) != (len - 8) as u64 {
            return Err(VerifyError::MalformedBlock);
        }
        let mut body = &block[8..len - 24];
        let mut offset = 8u64;
        let mut pairs = Vec::new();
        while !body.is_empty() {
            let invalid = |len| VerifyError::InvalidBlockPair { offset, len };
            let len_bytes: [u8; 8] = body.get(..8).ok_or(invalid(0))?.try_into().expect("eight bytes");
            let len = u64::from_le_bytes(len_bytes);
            let value_end = usize::try_from(len)
                .ok()
                .and_then(|len| len.checked_add(8))
                .filter(|&end| len >= 4 && end <= body.len());
            let end = value_end.ok_or(invalid(len))?;
            let id = BlockId(u32::from_le_bytes(body[8..12].try_into().expect("four bytes")));
            pairs.push((id, body[12..end].to_vec()));
            body = &body[end..];
            offset += end as u64;
        }
        Ok(Self { pairs })
    }

    /// The value of the first pair with `id`.
    pub fn get(&self, id: BlockId) -> Option<&[u8]> {
        self.pairs.iter().find(|(i, _)| *i == id).map(|(_, v)| v.as_slice())
    }

    /// Encodes the block with its size fields and magic.
    pub fn encode(&self) -> Vec<u8> {
        let pairs_len: usize = self.pairs.iter().map(|(_, v)| 12 + v.len()).sum();
        let size = (pairs_len + 8 + 16) as u64;
        let mut out = Vec::with_capacity(pairs_len + FRAME);
        out.extend_from_slice(&size.to_le_bytes());
        for (id, value) in &self.pairs {
            out.extend_from_slice(&(value.len() as u64 + 4).to_le_bytes());
            out.extend_from_slice(&id.0.to_le_bytes());
            out.extend_from_slice(value);
        }
        out.extend_from_slice(&size.to_le_bytes());
        out.extend_from_slice(&SIGNING_BLOCK_MAGIC);
        out
    }
}

/// A `u32` little-endian length followed by exactly that many bytes holding a `T`.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub(crate) struct Prefixed<T>(pub T);

impl<T> BinRead for Prefixed<T>
where
    T: BinRead,
{
    type Args<'a> = T::Args<'a>;

    fn read_options<R: Read + Seek>(reader: &mut R, endian: Endian, args: Self::Args<'_>) -> BinResult<Self> {
        let len = u32::read_options(reader, endian, ())?;
        let start = reader.stream_position()?;
        let mut bounded = reader.take_seek(u64::from(len));
        let value = T::read_options(&mut bounded, endian, args)?;
        if bounded.limit() != 0 {
            return Err(binrw::Error::AssertFail { pos: start, message: "record has trailing bytes".into() });
        }
        Ok(Self(value))
    }
}

impl<T> BinWrite for Prefixed<T>
where
    T: BinWrite,
{
    type Args<'a> = T::Args<'a>;

    fn write_options<W: Write + Seek>(&self, writer: &mut W, endian: Endian, args: Self::Args<'_>) -> BinResult<()> {
        let mut buffer = Cursor::new(Vec::new());
        self.0.write_options(&mut buffer, endian, args)?;
        let bytes = buffer.into_inner();
        let len = u32::try_from(bytes.len())
            .map_err(|_| binrw::Error::AssertFail { pos: 0, message: "record exceeds 4 GiB".into() })?;
        len.write_options(writer, endian, ())?;
        writer.write_all(&bytes)?;
        Ok(())
    }
}

/// Records repeated until the enclosing [`Prefixed`] region ends.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub(crate) struct Seq<T>(pub Vec<T>);

impl<T> BinRead for Seq<T>
where
    T: BinRead,
    for<'a> T::Args<'a>: Clone,
{
    type Args<'a> = T::Args<'a>;

    fn read_options<R: Read + Seek>(reader: &mut R, endian: Endian, args: Self::Args<'_>) -> BinResult<Self> {
        let start = reader.stream_position()?;
        let end = reader.seek(SeekFrom::End(0))?;
        reader.seek(SeekFrom::Start(start))?;
        let mut items = Vec::new();
        while reader.stream_position()? < end {
            items.push(T::read_options(reader, endian, args.clone())?);
        }
        Ok(Self(items))
    }
}

impl<T> BinWrite for Seq<T>
where
    T: BinWrite,
    for<'a> T::Args<'a>: Clone,
{
    type Args<'a> = T::Args<'a>;

    fn write_options<W: Write + Seek>(&self, writer: &mut W, endian: Endian, args: Self::Args<'_>) -> BinResult<()> {
        self.0.iter().try_for_each(|item| item.write_options(writer, endian, args.clone()))
    }
}

/// The remaining bytes of the enclosing [`Prefixed`] region.
#[derive(Clone, Default, Eq, PartialEq)]
pub(crate) struct Blob(pub Vec<u8>);

impl fmt::Debug for Blob {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Blob({} bytes)", self.0.len())
    }
}

impl BinRead for Blob {
    type Args<'a> = ();

    fn read_options<R: Read + Seek>(reader: &mut R, _: Endian, (): ()) -> BinResult<Self> {
        let mut bytes = Vec::new();
        reader.read_to_end(&mut bytes)?;
        Ok(Self(bytes))
    }
}

impl BinWrite for Blob {
    type Args<'a> = ();

    fn write_options<W: Write + Seek>(&self, writer: &mut W, _: Endian, (): ()) -> BinResult<()> {
        writer.write_all(&self.0)?;
        Ok(())
    }
}

/// Reads a `T` that must span all of `bytes`; returns the failing offset otherwise.
pub(crate) fn read_exact<T>(bytes: &[u8], args: T::Args<'_>) -> Result<T, u64>
where
    T: BinRead,
{
    let mut cursor = Cursor::new(bytes);
    let value = T::read_options(&mut cursor, Endian::Little, args).map_err(|error| error_offset(&error))?;
    if cursor.position() as usize != bytes.len() {
        return Err(cursor.position());
    }
    Ok(value)
}

/// Reads a `T` from the start of `bytes`, ignoring what follows it; returns
/// the failing offset otherwise.
pub(crate) fn read_leading<T>(bytes: &[u8], args: T::Args<'_>) -> Result<T, u64>
where
    T: BinRead,
{
    T::read_options(&mut Cursor::new(bytes), Endian::Little, args).map_err(|error| error_offset(&error))
}

/// Encodes a `T` into a new buffer.
pub(crate) fn write_vec<T>(value: &T, args: T::Args<'_>) -> Vec<u8>
where
    T: BinWrite,
{
    let mut cursor = Cursor::new(Vec::new());
    value.write_options(&mut cursor, Endian::Little, args).expect("signature records fit in memory and under 4 GiB");
    cursor.into_inner()
}

fn error_offset(error: &binrw::Error) -> u64 {
    match error {
        binrw::Error::BadMagic { pos, .. }
        | binrw::Error::AssertFail { pos, .. }
        | binrw::Error::Custom { pos, .. }
        | binrw::Error::NoVariantMatch { pos }
        | binrw::Error::EnumErrors { pos, .. } => *pos,
        binrw::Error::Backtrace(backtrace) => error_offset(&backtrace.error),
        _ => 0,
    }
}

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

    #[test]
    fn signing_block_round_trips_and_bounds_pairs() {
        let block = SigningBlock { pairs: vec![(BlockId::V2, vec![1, 2, 3]), (BlockId::V3, vec![])] };
        let bytes = block.encode();
        assert_eq!(SigningBlock::parse(&bytes).unwrap(), block);
        let mut corrupt = bytes.clone();
        corrupt[8..16].copy_from_slice(&u64::MAX.to_le_bytes());
        assert!(matches!(SigningBlock::parse(&corrupt), Err(VerifyError::InvalidBlockPair { offset: 8, .. })));
        for size_field in [0, bytes.len() - 24] {
            let mut corrupt = bytes.clone();
            corrupt[size_field..size_field + 8].copy_from_slice(&u64::MAX.to_le_bytes());
            assert!(matches!(SigningBlock::parse(&corrupt), Err(VerifyError::MalformedBlock)), "{size_field}");
        }
    }

    #[test]
    fn prefixed_records_must_be_exact() {
        // A Seq of two prefixed blobs: [len=1][a][len=2][b c]
        let bytes = [1, 0, 0, 0, b'a', 2, 0, 0, 0, b'b', b'c'];
        let parsed: Seq<Prefixed<Blob>> = read_exact(&bytes, ()).unwrap();
        assert_eq!(parsed.0.iter().map(|p| p.0.0.clone()).collect::<Vec<_>>(), [b"a".to_vec(), b"bc".to_vec()]);
        assert_eq!(write_vec(&parsed, ()), bytes);
        // A length that runs past the data.
        assert!(read_exact::<Prefixed<Blob>>(&[9, 0, 0, 0, b'x'], ()).is_err());
        // A u32 field inside a one-byte region.
        assert!(read_exact::<Prefixed<u32>>(&[1, 0, 0, 0, 7], ()).is_err());
        // Trailing bytes inside the region.
        assert!(read_exact::<Prefixed<u8>>(&[2, 0, 0, 0, 7, 7], ()).is_err());
    }
}