/* * 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::AsBytes; 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 { // 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{...} // // // // // [<#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::() + 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())) } }