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