1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 // `dm::verity` module implements the "verity" target in the device mapper framework. Specifically,
18 // it provides `DmVerityTargetBuilder` struct which is used to construct a `DmVerityTarget` struct
19 // which is then given to `DeviceMapper` to create a mapper device.
20 
21 use anyhow::{bail, Context, Result};
22 use std::io::Write;
23 use std::mem::size_of;
24 use std::path::Path;
25 use zerocopy::AsBytes;
26 
27 use crate::DmTargetSpec;
28 
29 // The UAPI for the verity target is here.
30 // https://www.kernel.org/doc/Documentation/device-mapper/verity.txt
31 
32 /// Device-Mapper’s “verity” target provides transparent integrity checking of block devices using
33 /// a cryptographic digest provided by the kernel crypto API
34 pub struct DmVerityTarget(Box<[u8]>);
35 
36 /// Version of the verity target spec.
37 pub enum DmVerityVersion {
38     /// Only `1` is supported.
39     V1,
40 }
41 
42 /// The hash algorithm to use. SHA256 and SHA512 are supported.
43 #[allow(dead_code)]
44 pub enum DmVerityHashAlgorithm {
45     /// sha with 256 bit hash
46     SHA256,
47     /// sha with 512 bit hash
48     SHA512,
49 }
50 
51 /// A builder that constructs `DmVerityTarget` struct.
52 pub struct DmVerityTargetBuilder<'a> {
53     version: DmVerityVersion,
54     data_device: Option<&'a Path>,
55     data_size: u64,
56     hash_device: Option<&'a Path>,
57     hash_algorithm: DmVerityHashAlgorithm,
58     root_digest: Option<&'a [u8]>,
59     salt: Option<&'a [u8]>,
60 }
61 
62 impl DmVerityTarget {
63     /// flatten into slice
as_slice(&self) -> &[u8]64     pub fn as_slice(&self) -> &[u8] {
65         self.0.as_ref()
66     }
67 }
68 
69 impl<'a> Default for DmVerityTargetBuilder<'a> {
default() -> Self70     fn default() -> Self {
71         DmVerityTargetBuilder {
72             version: DmVerityVersion::V1,
73             data_device: None,
74             data_size: 0,
75             hash_device: None,
76             hash_algorithm: DmVerityHashAlgorithm::SHA256,
77             root_digest: None,
78             salt: None,
79         }
80     }
81 }
82 
83 const BLOCK_SIZE: u64 = 4096;
84 
85 impl<'a> DmVerityTargetBuilder<'a> {
86     /// Sets the device that will be used as the data device (i.e. providing actual data).
data_device(&mut self, p: &'a Path, size: u64) -> &mut Self87     pub fn data_device(&mut self, p: &'a Path, size: u64) -> &mut Self {
88         self.data_device = Some(p);
89         self.data_size = size;
90         self
91     }
92 
93     /// Sets the device that provides the merkle tree.
hash_device(&mut self, p: &'a Path) -> &mut Self94     pub fn hash_device(&mut self, p: &'a Path) -> &mut Self {
95         self.hash_device = Some(p);
96         self
97     }
98 
99     /// Sets the hash algorithm that the merkle tree is using.
hash_algorithm(&mut self, algo: DmVerityHashAlgorithm) -> &mut Self100     pub fn hash_algorithm(&mut self, algo: DmVerityHashAlgorithm) -> &mut Self {
101         self.hash_algorithm = algo;
102         self
103     }
104 
105     /// Sets the root digest of the merkle tree. The format is hexadecimal string.
root_digest(&mut self, digest: &'a [u8]) -> &mut Self106     pub fn root_digest(&mut self, digest: &'a [u8]) -> &mut Self {
107         self.root_digest = Some(digest);
108         self
109     }
110 
111     /// Sets the salt used when creating the merkle tree. Note that this is empty for merkle trees
112     /// created following the APK signature scheme V4.
salt(&mut self, salt: &'a [u8]) -> &mut Self113     pub fn salt(&mut self, salt: &'a [u8]) -> &mut Self {
114         self.salt = Some(salt);
115         self
116     }
117 
118     /// Constructs a `DmVerityTarget`.
build(&self) -> Result<DmVerityTarget>119     pub fn build(&self) -> Result<DmVerityTarget> {
120         // The `DmVerityTarget` struct actually is a flattened data consisting of a header and
121         // body. The format of the header is `dm_target_spec` as defined in
122         // include/uapi/linux/dm-ioctl.h. The format of the body, in case of `verity` target is
123         // https://www.kernel.org/doc/Documentation/device-mapper/verity.txt
124         //
125         // Step 1: check the validity of the inputs and extra additional data (e.g. block size)
126         // from them.
127         let version = match self.version {
128             DmVerityVersion::V1 => 1,
129         };
130 
131         let data_device_path = self
132             .data_device
133             .context("data device is not set")?
134             .to_str()
135             .context("data device path is not encoded in utf8")?;
136         let data_block_size = BLOCK_SIZE;
137         let data_size = self.data_size;
138         let num_data_blocks = data_size / data_block_size;
139 
140         let hash_device_path = self
141             .hash_device
142             .context("hash device is not set")?
143             .to_str()
144             .context("hash device path is not encoded in utf8")?;
145         let hash_block_size = BLOCK_SIZE;
146 
147         let hash_algorithm = match self.hash_algorithm {
148             DmVerityHashAlgorithm::SHA256 => "sha256",
149             DmVerityHashAlgorithm::SHA512 => "sha512",
150         };
151 
152         let root_digest = if let Some(root_digest) = self.root_digest {
153             hex::encode(root_digest)
154         } else {
155             bail!("root digest is not set")
156         };
157 
158         let salt = if self.salt.is_none() || self.salt.unwrap().is_empty() {
159             "-".to_string() // Note. It's not an empty string!
160         } else {
161             hex::encode(self.salt.unwrap())
162         };
163 
164         // Step2: serialize the information according to the spec, which is ...
165         // DmTargetSpec{...}
166         // <version> <dev> <hash_dev>
167         // <data_block_size> <hash_block_size>
168         // <num_data_blocks> <hash_start_block>
169         // <algorithm> <digest> <salt>
170         // [<#opt_params> <opt_params>]
171         // null terminator
172 
173         // TODO(jiyong): support the optional parameters... if needed.
174         let mut body = String::new();
175         use std::fmt::Write;
176         write!(&mut body, "{} ", version)?;
177         write!(&mut body, "{} ", data_device_path)?;
178         write!(&mut body, "{} ", hash_device_path)?;
179         write!(&mut body, "{} ", data_block_size)?;
180         write!(&mut body, "{} ", hash_block_size)?;
181         write!(&mut body, "{} ", num_data_blocks)?;
182         write!(&mut body, "{} ", 0)?; // hash_start_block
183         write!(&mut body, "{} ", hash_algorithm)?;
184         write!(&mut body, "{} ", root_digest)?;
185         write!(&mut body, "{}", salt)?;
186         write!(&mut body, "\0")?; // null terminator
187 
188         let size = size_of::<DmTargetSpec>() + body.len();
189         let aligned_size = (size + 7) & !7; // align to 8 byte boundaries
190         let padding = aligned_size - size;
191         let mut header = DmTargetSpec::new("verity")?;
192         header.sector_start = 0;
193         header.length = data_size / 512; // number of 512-byte sectors
194         header.next = aligned_size as u32;
195 
196         let mut buf = Vec::with_capacity(aligned_size);
197         buf.write_all(header.as_bytes())?;
198         buf.write_all(body.as_bytes())?;
199         buf.write_all(vec![0; padding].as_slice())?;
200         Ok(DmVerityTarget(buf.into_boxed_slice()))
201     }
202 }
203