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