use crate::error::Error;
use crate::utils::Blob;
use crate::{crypto, P256KeyPair};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use num_traits::FromPrimitive;
use std::borrow::Cow;
use std::io::{self, Write};
use std::u32;
use uuid::Uuid;
#[derive(Debug, Copy, Clone)]
pub struct AppInfo {
type_: u32,
version: u32,
capabilities: u32,
product_id: Uuid,
}
impl AppInfo {
const LENGTH: usize = 28;
pub(crate) fn parse(mut bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() != 28 {
return Err(Error::parse_err(format!(
"invalid length for app info: {} bytes (expected 28)",
bytes.len()
)));
}
let type_ = bytes.read_u32::<LittleEndian>()?;
let version = bytes.read_u32::<LittleEndian>()?;
let capabilities = bytes.read_u32::<LittleEndian>()?;
let mut product_id = [0; 16];
product_id.copy_from_slice(bytes);
Ok(Self {
type_,
version,
capabilities,
product_id: Uuid::from_bytes(product_id),
})
}
pub(crate) fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
w.write_u32::<LittleEndian>(self.type_)?;
w.write_u32::<LittleEndian>(self.version)?;
w.write_u32::<LittleEndian>(self.capabilities)?;
w.write_all(self.product_id.as_bytes())?;
Ok(())
}
pub fn new(type_: u32, version: u32, capabilities: u32, product_id: Uuid) -> Self {
Self {
type_,
version,
capabilities,
product_id,
}
}
pub fn type_(&self) -> u32 {
self.type_
}
pub fn version(&self) -> u32 {
self.version
}
pub fn capabilities(&self) -> u32 {
self.capabilities
}
pub fn product_id(&self) -> Uuid {
self.product_id
}
}
#[derive(FromPrimitive, Debug, Copy, Clone)]
enum SignatureType {
None = 0,
EcdsaP256 = 1,
Crc32 = 2,
}
#[derive(Debug)]
pub struct AppProperties {
position: u32,
version: u32,
signature_type: SignatureType,
signature_location: u32,
app_info: AppInfo,
}
impl AppProperties {
const MAGIC: &'static [u8; 16] = &[
0x13, 0xB7, 0x79, 0xFA, 0xC9, 0x25, 0xDD, 0xB7, 0xAD, 0xF3, 0xCF, 0xE0, 0xF1, 0xB6, 0x14,
0xB8,
];
const LENGTH: usize = 16 + 3 * 4 + AppInfo::LENGTH;
const MAJOR_VERSION_SUPPORTED: u32 = 1;
const MAX_RAW_LEN: u32 = 0x80000000;
pub(crate) fn extract(image: &[u8]) -> Result<Self, Error> {
let complete_image = image;
if image.len() > Self::MAX_RAW_LEN as usize {
return Err(Error::parse_err(format!(
"could not get application properties from binary image (image larger than limit of {} bytes)",
Self::MAX_RAW_LEN
)));
}
if let Some(pos) = image
.windows(Self::MAGIC.len())
.position(|win| win == Self::MAGIC)
{
if image.len() < pos + Self::LENGTH {
return Err(Error::parse_err(
"could not get application properties from binary image (image too small)",
));
}
info!(
"app properties at {:#X}: {:?}",
pos,
Blob(&image[pos..pos + Self::LENGTH])
);
let mut image = &image[pos + Self::MAGIC.len()..];
let version = image.read_u32::<LittleEndian>()?;
let (v_major, v_minor) = (version >> 8, version & 0xff);
if v_major > Self::MAJOR_VERSION_SUPPORTED {
return Err(Error::parse_err(format!(
"unsupported app property struct version {}.{} (expected {}.x)",
v_major,
v_minor,
Self::MAJOR_VERSION_SUPPORTED
)));
}
let signature_type = image.read_u32::<LittleEndian>()?;
let signature_location = image.read_u32::<LittleEndian>()?;
let app_info = AppInfo::parse(&image[..AppInfo::LENGTH])?;
let signature_type = SignatureType::from_u32(signature_type).ok_or_else(|| {
Error::parse_err(format!(
"invalid signature type {} in app properties",
signature_type,
))
})?;
let signature_len = match signature_type {
SignatureType::None => 0,
SignatureType::Crc32 => {
return Err(Error::parse_err(r#"CRC-32 "signatures" not yet supported"#));
}
SignatureType::EcdsaP256 => 64,
};
if signature_location != 0 {
if signature_location as usize + signature_len > complete_image.len() {
return Err(Error::parse_err(format!(
"invalid signature location {:#010X} (length is {} bytes, image length is {:#010X})",
signature_location,
signature_len,
complete_image.len(),
)));
}
if let SignatureType::None = signature_type {
return Err(Error::parse_err(format!(
"non-zero signature location {:#010X} but signature type 'None'",
signature_location,
)));
}
}
let this = Self {
position: pos as u32,
version,
signature_type,
signature_location,
app_info,
};
debug!("parsed app properties: {:?}", this);
Ok(this)
} else {
Err(Error::parse_err(
"could not find application info in binary image",
))
}
}
fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
w.write_all(Self::MAGIC)?;
w.write_u32::<LittleEndian>(self.version)?;
w.write_u32::<LittleEndian>(self.signature_type as u32)?;
w.write_u32::<LittleEndian>(self.signature_location)?;
self.app_info.write(w)?;
Ok(())
}
pub(crate) fn app_info(&self) -> &AppInfo {
&self.app_info
}
}
#[derive(Debug)]
pub struct AppImage<'a> {
raw: Blob<Cow<'a, [u8]>>,
app_props_pos: u32,
app_props: AppProperties,
}
impl<'a> AppImage<'a> {
pub fn parse<T: AsRef<[u8]> + ?Sized>(image: &'a T) -> Result<Self, Error> {
let image = image.as_ref();
let props = AppProperties::extract(image)?;
Ok(Self {
raw: Blob(image.into()),
app_props_pos: props.position,
app_props: props,
})
}
pub fn into_raw(self) -> Cow<'a, [u8]> {
self.raw.0
}
pub fn app_info(&self) -> &AppInfo {
&self.app_props.app_info()
}
pub fn is_signed(&self) -> bool {
match (
self.app_props.signature_location,
&self.app_props.signature_type,
) {
(0, _) => false,
(_, SignatureType::EcdsaP256) => true,
_ => false,
}
}
pub fn ecdsa_signature(&self) -> Option<[u8; 64]> {
match (
self.app_props.signature_location,
&self.app_props.signature_type,
) {
(0, _) => None,
(pos, SignatureType::EcdsaP256) => {
let mut signature = [0; 64];
signature.copy_from_slice(&self.raw[pos as usize..pos as usize + 64]);
Some(signature)
}
_ => None,
}
}
pub fn sign(mut self, key: &P256KeyPair) -> Result<Self, Error> {
let sig_pos = self.make_ecdsa_signature_space() as usize;
let signature = crypto::create_signature(key, &self.raw[..sig_pos])?;
self.raw.0.to_mut()[sig_pos..sig_pos + 64].copy_from_slice(&signature);
Ok(self)
}
fn make_ecdsa_signature_space(&mut self) -> u32 {
if let SignatureType::EcdsaP256 = self.app_props.signature_type {
if self.app_props.signature_location != 0 {
return self.app_props.signature_location;
}
}
let location = self.raw.len() as u32;
let mut raw = vec![0; self.raw.len() + 64];
raw[..self.raw.len()].copy_from_slice(&self.raw);
self.app_props.signature_type = SignatureType::EcdsaP256;
self.app_props.signature_location = location;
let props_pos = self.app_props_pos as usize;
self.app_props
.write(&mut &mut raw[props_pos..props_pos + AppProperties::LENGTH])
.expect("serializing properties to byte slice failed");
self.raw = Blob(Cow::Owned(raw));
location
}
}