blob: c3f491c0a4f73e458ca7a1422c1b407586fc80c5 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
use anyhow::{ensure, Result};
use memoffset::offset_of;
use std::convert::TryInto;
use std::fs::File;
use std::io::{Read, Write};
use std::mem::size_of;
use std::path::{Path, PathBuf};
use thiserror::Error;
use zerocopy::LayoutVerified;
use crate::crypto::rsa::{Modulus, Signature};
use crate::crypto::sha256;
use crate::image::manifest::Manifest;
use crate::image::manifest_def::{ManifestRsaBuffer, ManifestSpec};
use crate::util::parse_int::ParseInt;
#[derive(Debug, Error)]
pub enum ImageError {
#[error("Incomplete read: expected to read {0} bytes but read {1} bytes")]
IncompleteRead(usize, usize),
#[error("Failed to parse image manifest.")]
Parse,
}
/// A buffer with the same alignment as `Manifest` for storing image data.
#[repr(C)]
#[derive(Debug)]
pub struct ImageData {
pub bytes: [u8; Image::MAX_SIZE],
_align: [Manifest; 0],
}
impl Default for ImageData {
fn default() -> Self {
ImageData {
bytes: [0xFF; Image::MAX_SIZE],
_align: [],
}
}
}
#[derive(Debug, Default)]
pub struct Image {
data: Box<ImageData>,
size: usize,
}
#[derive(Debug)]
pub enum ImageChunk {
Concat(PathBuf),
Offset(PathBuf, usize),
}
#[derive(Debug, Default)]
pub struct ImageAssembler {
pub size: usize,
pub mirrored: bool,
pub chunks: Vec<ImageChunk>,
}
impl Image {
const MAX_SIZE: usize = 512 * 1024;
/// Reads in an `Image`.
pub fn from_reader(mut r: impl Read) -> Result<Self> {
let mut image = Image::default();
image.size = r.read(&mut image.data.bytes)?;
Ok(image)
}
/// Writes out the `Image`.
pub fn to_writer(&self, w: &mut impl Write) -> Result<()> {
w.write_all(&self.data.bytes[..self.size])?;
Ok(())
}
/// Creates an `Image` from a given input binary.
pub fn read_from_file(path: &Path) -> Result<Image> {
let file = File::open(path)?;
Self::from_reader(file)
}
/// Write out the `Image` to a file at the given `path`.
pub fn write_to_file(self, path: &Path) -> Result<()> {
let mut file = File::create(path)?;
self.to_writer(&mut file)
}
/// Overwrites all fields in the image's manifest that are defined in `other`.
pub fn overwrite_manifest(&mut self, other: ManifestSpec) -> Result<()> {
let manifest = self.borrow_manifest_mut()?;
let mut manifest_def: ManifestSpec = (&*manifest).try_into()?;
manifest_def.overwrite_fields(other);
*manifest = manifest_def.try_into()?;
Ok(())
}
/// Updates the signature field in the `Manifest`.
pub fn update_signature(&mut self, signature: Signature) -> Result<()> {
let manifest = self.borrow_manifest_mut()?;
// Convert to a `ManifestSpec` so we can supply the signature as a `BigInt`.
let mut manifest_def: ManifestSpec = (&*manifest).try_into()?;
manifest_def.update_signature(ManifestRsaBuffer::from_le_bytes(signature.to_le_bytes())?);
*manifest = manifest_def.try_into()?;
Ok(())
}
/// Updates the modulus field in the `Manifest`.
pub fn update_modulus(&mut self, modulus: Modulus) -> Result<()> {
let manifest = self.borrow_manifest_mut()?;
// Convert to a `ManifestSpec` so we can supply the modulus as a `BigInt`.
let mut manifest_def: ManifestSpec = (&*manifest).try_into()?;
manifest_def.update_modulus(ManifestRsaBuffer::from_le_bytes(modulus.to_le_bytes())?);
*manifest = manifest_def.try_into()?;
Ok(())
}
pub fn borrow_manifest(&self) -> Result<&Manifest> {
let manifest_slice = &self.data.bytes[0..size_of::<Manifest>()];
let manifest_layout: LayoutVerified<&[u8], Manifest> =
LayoutVerified::new(manifest_slice).ok_or(ImageError::Parse)?;
let manifest: &Manifest = manifest_layout.into_ref();
Ok(manifest)
}
pub fn borrow_manifest_mut(&mut self) -> Result<&mut Manifest> {
let manifest_slice = &mut self.data.bytes[0..size_of::<Manifest>()];
let manifest_layout: LayoutVerified<&mut [u8], Manifest> =
LayoutVerified::new(&mut *manifest_slice).ok_or(ImageError::Parse)?;
let manifest: &mut Manifest = manifest_layout.into_mut();
Ok(manifest)
}
/// Updates the length field in the `Manifest` to the length of the image.
pub fn update_length(&mut self) -> Result<usize> {
let length = self.size as u32;
let m = self.borrow_manifest_mut()?;
m.length = length;
Ok(self.size)
}
/// Compute the SHA256 digest for the signed portion of the `Image`.
pub fn compute_digest(&self) -> sha256::Sha256Digest {
sha256::sha256(&self.data.bytes[offset_of!(Manifest, usage_constraints)..self.size])
}
}
impl ImageAssembler {
/// Creates an `ImageAssembler` with a given `size` and mirroring parameters.
pub fn with_params(size: usize, mirrored: bool) -> Self {
ImageAssembler {
size,
mirrored,
..Default::default()
}
}
/// Creates an `ImageAssembler` with default parameters for OpenTitan: a 1MiB image which is mirrored.
pub fn new() -> Self {
Self::with_params(0x100000, true)
}
/// Parse a list of strings into chunks to be assembled.
/// Each string may be a filename or a filename@offset describing where in the assembled image the contents of the file should appear.
/// The offset is an integer expressed in any of the bases accepted by [`ParseInt`].
pub fn parse(&mut self, chunks: &[impl AsRef<str>]) -> Result<()> {
for chunk in chunks {
if let Some((file, offset)) = chunk.as_ref().split_once('@') {
self.chunks.push(ImageChunk::Offset(
PathBuf::from(file),
usize::from_str(offset)?,
));
} else {
self.chunks
.push(ImageChunk::Concat(PathBuf::from(chunk.as_ref())));
}
}
Ok(())
}
// Read a file into a buffer. Ensure the entire file is read.
fn read(path: &Path, buf: &mut [u8]) -> Result<usize> {
let mut file = File::open(path)?;
let len = file.metadata()?.len() as usize;
let n = file.read(buf)?;
ensure!(len == n, ImageError::IncompleteRead(len, n));
Ok(n)
}
/// Assemble the image according to the parameters and parsed chunk specifications.
pub fn assemble(&self) -> Result<Vec<u8>> {
let size = if self.mirrored {
self.size / 2
} else {
self.size
};
let mut image = vec![0xff; size];
let mut pos = 0;
for chunk in &self.chunks {
match chunk {
ImageChunk::Concat(path) => {
let n = Self::read(path, &mut image[pos..])?;
pos += n;
}
ImageChunk::Offset(path, offset) => {
let n = Self::read(path, &mut image[*offset..])?;
pos = offset + n;
}
}
}
if self.mirrored {
image.extend_from_within(..size);
}
Ok(image)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testdata;
#[test]
fn test_assemble_concat() -> Result<()> {
// Test image assembly by concatenation.
let mut image = ImageAssembler::with_params(16, false);
image.parse(&[
testdata!("hello.txt").to_str().unwrap(),
testdata!("world.txt").to_str().unwrap(),
])?;
let data = image.assemble()?;
assert_eq!(data, b"HelloWorld\xff\xff\xff\xff\xff\xff");
Ok(())
}
#[test]
fn test_assemble_offset() -> Result<()> {
// Test image assembly by explicit offsets.
let mut image = ImageAssembler::with_params(16, false);
image.parse(&[
testdata!("hello.txt@0").to_str().unwrap(),
testdata!("world.txt@0x8").to_str().unwrap(),
])?;
let data = image.assemble()?;
assert_eq!(data, b"Hello\xff\xff\xffWorld\xff\xff\xff");
Ok(())
}
#[test]
fn test_assemble_mirrored() -> Result<()> {
// Test image assembly with mirroring.
let mut image = ImageAssembler::with_params(20, true);
image.parse(&[
testdata!("hello.txt").to_str().unwrap(),
testdata!("world.txt").to_str().unwrap(),
])?;
let data = image.assemble()?;
assert_eq!(data, b"HelloWorldHelloWorld");
Ok(())
}
#[test]
fn test_assemble_mirrored_offset_error() -> Result<()> {
// Test image assembly where one of the source files isn't read completely.
let mut image = ImageAssembler::with_params(16, true);
image.parse(&[
testdata!("hello.txt@0").to_str().unwrap(),
testdata!("world.txt@0x5").to_str().unwrap(),
])?;
let err = image.assemble().unwrap_err();
assert_eq!(
err.to_string(),
"Incomplete read: expected to read 5 bytes but read 3 bytes"
);
Ok(())
}
#[test]
fn test_load_image() {
// Read and write back image.
let image = Image::read_from_file(&testdata!("test_image.bin")).unwrap();
image
.write_to_file(&testdata!("test_image_out.bin"))
.unwrap();
// Ensure the result is identical to the original.
let (mut orig_bytes, mut res_bytes) = (Vec::<u8>::new(), Vec::<u8>::new());
File::open(&testdata!("test_image.bin"))
.unwrap()
.read_to_end(&mut orig_bytes)
.unwrap();
File::open(&testdata!("test_image_out.bin"))
.unwrap()
.read_to_end(&mut res_bytes)
.unwrap();
assert_eq!(orig_bytes, res_bytes);
}
}