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;
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct BlockId(pub u32);
impl BlockId {
pub const V2: Self = Self(0x7109_871a);
pub const V3: Self = Self(0xf053_68c0);
pub const V3_1: Self = Self(0x1b93_ad61);
pub const VERITY_PADDING: Self = Self(0x4272_6577);
pub const DEPENDENCY_INFO: Self = Self(0x504b_4453);
pub const SOURCE_STAMP: Self = Self(0x6dff_800d);
pub const SOURCE_STAMP_V1: Self = Self(0x2b09_189e);
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)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct SigningBlock {
pub pairs: Vec<(BlockId, Vec<u8>)>,
}
const FRAME: usize = 8 + 8 + 16;
impl SigningBlock {
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 })
}
pub fn get(&self, id: BlockId) -> Option<&[u8]> {
self.pairs.iter().find(|(i, _)| *i == id).map(|(_, v)| v.as_slice())
}
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
}
}
#[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(())
}
}
#[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()))
}
}
#[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(())
}
}
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)
}
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))
}
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() {
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);
assert!(read_exact::<Prefixed<Blob>>(&[9, 0, 0, 0, b'x'], ()).is_err());
assert!(read_exact::<Prefixed<u32>>(&[1, 0, 0, 0, 7], ()).is_err());
assert!(read_exact::<Prefixed<u8>>(&[2, 0, 0, 0, 7, 7], ()).is_err());
}
}