blob: bbd9d38db98cad56bb3d2aed3ff2eb829e94853f [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;
Frederick Mayle8f795902023-10-23 15:48:34 -070025use zerocopy::AsBytes;
Jiyong Park86c9b082021-06-04 19:03:48 +090026
Jiyong Park86c9b082021-06-04 19:03:48 +090027use crate::util::*;
Shikha Panwar414ea892022-10-12 13:45:52 +000028use crate::DmTargetSpec;
Jiyong Park86c9b082021-06-04 19:03:48 +090029
30// The UAPI for the verity target is here.
31// https://www.kernel.org/doc/Documentation/device-mapper/verity.txt
32
Shikha Panwar414ea892022-10-12 13:45:52 +000033/// Device-Mapper’s “verity” target provides transparent integrity checking of block devices using
34/// a cryptographic digest provided by the kernel crypto API
35pub struct DmVerityTarget(Box<[u8]>);
36
37/// Version of the verity target spec.
Jiyong Park86c9b082021-06-04 19:03:48 +090038pub enum DmVerityVersion {
Shikha Panwar414ea892022-10-12 13:45:52 +000039 /// Only `1` is supported.
Jiyong Park86c9b082021-06-04 19:03:48 +090040 V1,
41}
42
43/// The hash algorithm to use. SHA256 and SHA512 are supported.
Jiyong Park99a35b82021-06-07 10:13:44 +090044#[allow(dead_code)]
Jiyong Park86c9b082021-06-04 19:03:48 +090045pub enum DmVerityHashAlgorithm {
Shikha Panwar414ea892022-10-12 13:45:52 +000046 /// sha with 256 bit hash
Jiyong Park86c9b082021-06-04 19:03:48 +090047 SHA256,
Shikha Panwar414ea892022-10-12 13:45:52 +000048 /// sha with 512 bit hash
Jiyong Park86c9b082021-06-04 19:03:48 +090049 SHA512,
50}
51
52/// A builder that constructs `DmVerityTarget` struct.
53pub struct DmVerityTargetBuilder<'a> {
54 version: DmVerityVersion,
55 data_device: Option<&'a Path>,
56 data_size: u64,
57 hash_device: Option<&'a Path>,
58 hash_algorithm: DmVerityHashAlgorithm,
59 root_digest: Option<&'a [u8]>,
60 salt: Option<&'a [u8]>,
61}
62
Jiyong Park86c9b082021-06-04 19:03:48 +090063impl DmVerityTarget {
Shikha Panwar414ea892022-10-12 13:45:52 +000064 /// flatten into slice
Jiyong Park3c327d22021-06-08 20:51:54 +090065 pub fn as_slice(&self) -> &[u8] {
Jiyong Park86c9b082021-06-04 19:03:48 +090066 self.0.as_ref()
67 }
68}
69
70impl<'a> Default for DmVerityTargetBuilder<'a> {
71 fn default() -> Self {
72 DmVerityTargetBuilder {
73 version: DmVerityVersion::V1,
74 data_device: None,
75 data_size: 0,
76 hash_device: None,
77 hash_algorithm: DmVerityHashAlgorithm::SHA256,
78 root_digest: None,
79 salt: None,
80 }
81 }
82}
83
84impl<'a> DmVerityTargetBuilder<'a> {
85 /// Sets the device that will be used as the data device (i.e. providing actual data).
86 pub fn data_device(&mut self, p: &'a Path, size: u64) -> &mut Self {
87 self.data_device = Some(p);
88 self.data_size = size;
89 self
90 }
91
92 /// Sets the device that provides the merkle tree.
93 pub fn hash_device(&mut self, p: &'a Path) -> &mut Self {
94 self.hash_device = Some(p);
95 self
96 }
97
Shikha Panwar414ea892022-10-12 13:45:52 +000098 /// Sets the hash algorithm that the merkle tree is using.
Jiyong Park86c9b082021-06-04 19:03:48 +090099 pub fn hash_algorithm(&mut self, algo: DmVerityHashAlgorithm) -> &mut Self {
100 self.hash_algorithm = algo;
101 self
102 }
103
104 /// Sets the root digest of the merkle tree. The format is hexadecimal string.
105 pub fn root_digest(&mut self, digest: &'a [u8]) -> &mut Self {
106 self.root_digest = Some(digest);
107 self
108 }
109
110 /// Sets the salt used when creating the merkle tree. Note that this is empty for merkle trees
111 /// created following the APK signature scheme V4.
112 pub fn salt(&mut self, salt: &'a [u8]) -> &mut Self {
113 self.salt = Some(salt);
114 self
115 }
116
117 /// Constructs a `DmVerityTarget`.
118 pub fn build(&self) -> Result<DmVerityTarget> {
119 // The `DmVerityTarget` struct actually is a flattened data consisting of a header and
120 // body. The format of the header is `dm_target_spec` as defined in
121 // include/uapi/linux/dm-ioctl.h. The format of the body, in case of `verity` target is
122 // https://www.kernel.org/doc/Documentation/device-mapper/verity.txt
123 //
124 // Step 1: check the validity of the inputs and extra additional data (e.g. block size)
125 // from them.
126 let version = match self.version {
127 DmVerityVersion::V1 => 1,
128 };
129
130 let data_device_path = self
131 .data_device
132 .context("data device is not set")?
133 .to_str()
134 .context("data device path is not encoded in utf8")?;
135 let stat = fstat(self.data_device.unwrap())?; // safe; checked just above
136 let data_block_size = stat.st_blksize as u64;
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 stat = fstat(self.data_device.unwrap())?; // safe; checked just above
146 let hash_block_size = stat.st_blksize;
147
148 let hash_algorithm = match self.hash_algorithm {
149 DmVerityHashAlgorithm::SHA256 => "sha256",
150 DmVerityHashAlgorithm::SHA512 => "sha512",
151 };
152
153 let root_digest = if let Some(root_digest) = self.root_digest {
Alice Wang8ed29ce2023-12-01 08:33:12 +0000154 hex::encode(root_digest)
Jiyong Park86c9b082021-06-04 19:03:48 +0900155 } else {
156 bail!("root digest is not set")
157 };
158
159 let salt = if self.salt.is_none() || self.salt.unwrap().is_empty() {
160 "-".to_string() // Note. It's not an empty string!
161 } else {
Alice Wang8ed29ce2023-12-01 08:33:12 +0000162 hex::encode(self.salt.unwrap())
Jiyong Park86c9b082021-06-04 19:03:48 +0900163 };
164
165 // Step2: serialize the information according to the spec, which is ...
166 // DmTargetSpec{...}
167 // <version> <dev> <hash_dev>
168 // <data_block_size> <hash_block_size>
169 // <num_data_blocks> <hash_start_block>
170 // <algorithm> <digest> <salt>
171 // [<#opt_params> <opt_params>]
172 // null terminator
173
174 // TODO(jiyong): support the optional parameters... if needed.
175 let mut body = String::new();
176 use std::fmt::Write;
177 write!(&mut body, "{} ", version)?;
178 write!(&mut body, "{} ", data_device_path)?;
179 write!(&mut body, "{} ", hash_device_path)?;
180 write!(&mut body, "{} ", data_block_size)?;
181 write!(&mut body, "{} ", hash_block_size)?;
182 write!(&mut body, "{} ", num_data_blocks)?;
183 write!(&mut body, "{} ", 0)?; // hash_start_block
184 write!(&mut body, "{} ", hash_algorithm)?;
185 write!(&mut body, "{} ", root_digest)?;
186 write!(&mut body, "{}", salt)?;
187 write!(&mut body, "\0")?; // null terminator
188
189 let size = size_of::<DmTargetSpec>() + body.len();
190 let aligned_size = (size + 7) & !7; // align to 8 byte boundaries
191 let padding = aligned_size - size;
192 let mut header = DmTargetSpec::new("verity")?;
193 header.sector_start = 0;
194 header.length = data_size / 512; // number of 512-byte sectors
195 header.next = aligned_size as u32;
196
197 let mut buf = Vec::with_capacity(aligned_size);
Frederick Mayle8f795902023-10-23 15:48:34 -0700198 buf.write_all(header.as_bytes())?;
Jiyong Park86c9b082021-06-04 19:03:48 +0900199 buf.write_all(body.as_bytes())?;
200 buf.write_all(vec![0; padding].as_slice())?;
201 Ok(DmVerityTarget(buf.into_boxed_slice()))
202 }
203}