blob: 09087da36aa42c0b4bcb3b092b0a948172322fa7 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// `dm::verity` module implements the "verity" target in the device mapper framework. Specifically,
// it provides `DmVerityTargetBuilder` struct which is used to construct a `DmVerityTarget` struct
// which is then given to `DeviceMapper` to create a mapper device.
use anyhow::{bail, Context, Result};
use std::io::Write;
use std::mem::size_of;
use std::path::Path;
use zerocopy::IntoBytes;
use crate::DmTargetSpec;
// The UAPI for the verity target is here.
// https://www.kernel.org/doc/Documentation/device-mapper/verity.txt
/// Device-Mapper’s “verity” target provides transparent integrity checking of block devices using
/// a cryptographic digest provided by the kernel crypto API
pub struct DmVerityTarget(Box<[u8]>);
/// Version of the verity target spec.
pub enum DmVerityVersion {
/// Only `1` is supported.
V1,
}
/// The hash algorithm to use. SHA256 and SHA512 are supported.
#[allow(dead_code)]
pub enum DmVerityHashAlgorithm {
/// sha with 256 bit hash
SHA256,
/// sha with 512 bit hash
SHA512,
}
/// A builder that constructs `DmVerityTarget` struct.
pub struct DmVerityTargetBuilder<'a> {
version: DmVerityVersion,
data_device: Option<&'a Path>,
data_size: u64,
hash_device: Option<&'a Path>,
hash_algorithm: DmVerityHashAlgorithm,
root_digest: Option<&'a [u8]>,
salt: Option<&'a [u8]>,
}
impl DmVerityTarget {
/// flatten into slice
pub fn as_slice(&self) -> &[u8] {
self.0.as_ref()
}
}
impl<'a> Default for DmVerityTargetBuilder<'a> {
fn default() -> Self {
DmVerityTargetBuilder {
version: DmVerityVersion::V1,
data_device: None,
data_size: 0,
hash_device: None,
hash_algorithm: DmVerityHashAlgorithm::SHA256,
root_digest: None,
salt: None,
}
}
}
const BLOCK_SIZE: u64 = 4096;
impl<'a> DmVerityTargetBuilder<'a> {
/// Sets the device that will be used as the data device (i.e. providing actual data).
pub fn data_device(&mut self, p: &'a Path, size: u64) -> &mut Self {
self.data_device = Some(p);
self.data_size = size;
self
}
/// Sets the device that provides the merkle tree.
pub fn hash_device(&mut self, p: &'a Path) -> &mut Self {
self.hash_device = Some(p);
self
}
/// Sets the hash algorithm that the merkle tree is using.
pub fn hash_algorithm(&mut self, algo: DmVerityHashAlgorithm) -> &mut Self {
self.hash_algorithm = algo;
self
}
/// Sets the root digest of the merkle tree. The format is hexadecimal string.
pub fn root_digest(&mut self, digest: &'a [u8]) -> &mut Self {
self.root_digest = Some(digest);
self
}
/// Sets the salt used when creating the merkle tree. Note that this is empty for merkle trees
/// created following the APK signature scheme V4.
pub fn salt(&mut self, salt: &'a [u8]) -> &mut Self {
self.salt = Some(salt);
self
}
/// Constructs a `DmVerityTarget`.
pub fn build(&self) -> Result<DmVerityTarget> {
// The `DmVerityTarget` struct actually is a flattened data consisting of a header and
// body. The format of the header is `dm_target_spec` as defined in
// include/uapi/linux/dm-ioctl.h. The format of the body, in case of `verity` target is
// https://www.kernel.org/doc/Documentation/device-mapper/verity.txt
//
// Step 1: check the validity of the inputs and extra additional data (e.g. block size)
// from them.
let version = match self.version {
DmVerityVersion::V1 => 1,
};
let data_device_path = self
.data_device
.context("data device is not set")?
.to_str()
.context("data device path is not encoded in utf8")?;
let data_block_size = BLOCK_SIZE;
let data_size = self.data_size;
let num_data_blocks = data_size / data_block_size;
let hash_device_path = self
.hash_device
.context("hash device is not set")?
.to_str()
.context("hash device path is not encoded in utf8")?;
let hash_block_size = BLOCK_SIZE;
let hash_algorithm = match self.hash_algorithm {
DmVerityHashAlgorithm::SHA256 => "sha256",
DmVerityHashAlgorithm::SHA512 => "sha512",
};
let root_digest = if let Some(root_digest) = self.root_digest {
hex::encode(root_digest)
} else {
bail!("root digest is not set")
};
let salt = if self.salt.is_none() || self.salt.unwrap().is_empty() {
"-".to_string() // Note. It's not an empty string!
} else {
hex::encode(self.salt.unwrap())
};
// Step2: serialize the information according to the spec, which is ...
// DmTargetSpec{...}
// <version> <dev> <hash_dev>
// <data_block_size> <hash_block_size>
// <num_data_blocks> <hash_start_block>
// <algorithm> <digest> <salt>
// [<#opt_params> <opt_params>]
// null terminator
// TODO(jiyong): support the optional parameters... if needed.
let mut body = String::new();
use std::fmt::Write;
write!(&mut body, "{} ", version)?;
write!(&mut body, "{} ", data_device_path)?;
write!(&mut body, "{} ", hash_device_path)?;
write!(&mut body, "{} ", data_block_size)?;
write!(&mut body, "{} ", hash_block_size)?;
write!(&mut body, "{} ", num_data_blocks)?;
write!(&mut body, "{} ", 0)?; // hash_start_block
write!(&mut body, "{} ", hash_algorithm)?;
write!(&mut body, "{} ", root_digest)?;
write!(&mut body, "{}", salt)?;
write!(&mut body, "\0")?; // null terminator
let size = size_of::<DmTargetSpec>() + body.len();
let aligned_size = (size + 7) & !7; // align to 8 byte boundaries
let padding = aligned_size - size;
let mut header = DmTargetSpec::new("verity")?;
header.sector_start = 0;
header.length = data_size / 512; // number of 512-byte sectors
header.next = aligned_size as u32;
let mut buf = Vec::with_capacity(aligned_size);
buf.write_all(header.as_bytes())?;
buf.write_all(body.as_bytes())?;
buf.write_all(vec![0; padding].as_slice())?;
Ok(DmVerityTarget(buf.into_boxed_slice()))
}
}