blob: 3a49ee2ef87a1926428b94255a872f91772497f8 [file] [log] [blame]
Jiyong Park86c9b082021-06-04 19:03:48 +09001/*
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
21use anyhow::{bail, Context, Result};
Jiyong Park3c327d22021-06-08 20:51:54 +090022use data_model::DataInit;
Jiyong Park86c9b082021-06-04 19:03:48 +090023use std::io::Write;
24use std::mem::size_of;
25use std::path::Path;
26
27use super::DmTargetSpec;
28use crate::util::*;
29
30// The UAPI for the verity target is here.
31// https://www.kernel.org/doc/Documentation/device-mapper/verity.txt
32
33/// Version of the verity target spec. Only `V1` is supported.
34pub enum DmVerityVersion {
35 V1,
36}
37
38/// The hash algorithm to use. SHA256 and SHA512 are supported.
Jiyong Park99a35b82021-06-07 10:13:44 +090039#[allow(dead_code)]
Jiyong Park86c9b082021-06-04 19:03:48 +090040pub enum DmVerityHashAlgorithm {
41 SHA256,
42 SHA512,
43}
44
45/// A builder that constructs `DmVerityTarget` struct.
46pub struct DmVerityTargetBuilder<'a> {
47 version: DmVerityVersion,
48 data_device: Option<&'a Path>,
49 data_size: u64,
50 hash_device: Option<&'a Path>,
51 hash_algorithm: DmVerityHashAlgorithm,
52 root_digest: Option<&'a [u8]>,
53 salt: Option<&'a [u8]>,
54}
55
56pub struct DmVerityTarget(Box<[u8]>);
57
58impl DmVerityTarget {
Jiyong Park3c327d22021-06-08 20:51:54 +090059 pub fn as_slice(&self) -> &[u8] {
Jiyong Park86c9b082021-06-04 19:03:48 +090060 self.0.as_ref()
61 }
62}
63
64impl<'a> Default for DmVerityTargetBuilder<'a> {
65 fn default() -> Self {
66 DmVerityTargetBuilder {
67 version: DmVerityVersion::V1,
68 data_device: None,
69 data_size: 0,
70 hash_device: None,
71 hash_algorithm: DmVerityHashAlgorithm::SHA256,
72 root_digest: None,
73 salt: None,
74 }
75 }
76}
77
78impl<'a> DmVerityTargetBuilder<'a> {
79 /// Sets the device that will be used as the data device (i.e. providing actual data).
80 pub fn data_device(&mut self, p: &'a Path, size: u64) -> &mut Self {
81 self.data_device = Some(p);
82 self.data_size = size;
83 self
84 }
85
86 /// Sets the device that provides the merkle tree.
87 pub fn hash_device(&mut self, p: &'a Path) -> &mut Self {
88 self.hash_device = Some(p);
89 self
90 }
91
92 /// Sets the hash algorithm that the merkel tree is using.
93 pub fn hash_algorithm(&mut self, algo: DmVerityHashAlgorithm) -> &mut Self {
94 self.hash_algorithm = algo;
95 self
96 }
97
98 /// Sets the root digest of the merkle tree. The format is hexadecimal string.
99 pub fn root_digest(&mut self, digest: &'a [u8]) -> &mut Self {
100 self.root_digest = Some(digest);
101 self
102 }
103
104 /// Sets the salt used when creating the merkle tree. Note that this is empty for merkle trees
105 /// created following the APK signature scheme V4.
106 pub fn salt(&mut self, salt: &'a [u8]) -> &mut Self {
107 self.salt = Some(salt);
108 self
109 }
110
111 /// Constructs a `DmVerityTarget`.
112 pub fn build(&self) -> Result<DmVerityTarget> {
113 // The `DmVerityTarget` struct actually is a flattened data consisting of a header and
114 // body. The format of the header is `dm_target_spec` as defined in
115 // include/uapi/linux/dm-ioctl.h. The format of the body, in case of `verity` target is
116 // https://www.kernel.org/doc/Documentation/device-mapper/verity.txt
117 //
118 // Step 1: check the validity of the inputs and extra additional data (e.g. block size)
119 // from them.
120 let version = match self.version {
121 DmVerityVersion::V1 => 1,
122 };
123
124 let data_device_path = self
125 .data_device
126 .context("data device is not set")?
127 .to_str()
128 .context("data device path is not encoded in utf8")?;
129 let stat = fstat(self.data_device.unwrap())?; // safe; checked just above
130 let data_block_size = stat.st_blksize as u64;
131 let data_size = self.data_size;
132 let num_data_blocks = data_size / data_block_size;
133
134 let hash_device_path = self
135 .hash_device
136 .context("hash device is not set")?
137 .to_str()
138 .context("hash device path is not encoded in utf8")?;
139 let stat = fstat(self.data_device.unwrap())?; // safe; checked just above
140 let hash_block_size = stat.st_blksize;
141
142 let hash_algorithm = match self.hash_algorithm {
143 DmVerityHashAlgorithm::SHA256 => "sha256",
144 DmVerityHashAlgorithm::SHA512 => "sha512",
145 };
146
147 let root_digest = if let Some(root_digest) = self.root_digest {
148 hexstring_from(root_digest)
149 } else {
150 bail!("root digest is not set")
151 };
152
153 let salt = if self.salt.is_none() || self.salt.unwrap().is_empty() {
154 "-".to_string() // Note. It's not an empty string!
155 } else {
156 hexstring_from(self.salt.unwrap())
157 };
158
159 // Step2: serialize the information according to the spec, which is ...
160 // DmTargetSpec{...}
161 // <version> <dev> <hash_dev>
162 // <data_block_size> <hash_block_size>
163 // <num_data_blocks> <hash_start_block>
164 // <algorithm> <digest> <salt>
165 // [<#opt_params> <opt_params>]
166 // null terminator
167
168 // TODO(jiyong): support the optional parameters... if needed.
169 let mut body = String::new();
170 use std::fmt::Write;
171 write!(&mut body, "{} ", version)?;
172 write!(&mut body, "{} ", data_device_path)?;
173 write!(&mut body, "{} ", hash_device_path)?;
174 write!(&mut body, "{} ", data_block_size)?;
175 write!(&mut body, "{} ", hash_block_size)?;
176 write!(&mut body, "{} ", num_data_blocks)?;
177 write!(&mut body, "{} ", 0)?; // hash_start_block
178 write!(&mut body, "{} ", hash_algorithm)?;
179 write!(&mut body, "{} ", root_digest)?;
180 write!(&mut body, "{}", salt)?;
181 write!(&mut body, "\0")?; // null terminator
182
183 let size = size_of::<DmTargetSpec>() + body.len();
184 let aligned_size = (size + 7) & !7; // align to 8 byte boundaries
185 let padding = aligned_size - size;
186 let mut header = DmTargetSpec::new("verity")?;
187 header.sector_start = 0;
188 header.length = data_size / 512; // number of 512-byte sectors
189 header.next = aligned_size as u32;
190
191 let mut buf = Vec::with_capacity(aligned_size);
Jiyong Park3c327d22021-06-08 20:51:54 +0900192 buf.write_all(header.as_slice())?;
Jiyong Park86c9b082021-06-04 19:03:48 +0900193 buf.write_all(body.as_bytes())?;
194 buf.write_all(vec![0; padding].as_slice())?;
195 Ok(DmVerityTarget(buf.into_boxed_slice()))
196 }
197}