blob: 09087da36aa42c0b4bcb3b092b0a948172322fa7 [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;
Andrew Walbran47d316e2024-11-28 18:41:09 +000025use zerocopy::IntoBytes;
Jiyong Park86c9b082021-06-04 19:03:48 +090026
Shikha Panwar414ea892022-10-12 13:45:52 +000027use crate::DmTargetSpec;
Jiyong Park86c9b082021-06-04 19:03:48 +090028
29// The UAPI for the verity target is here.
30// https://www.kernel.org/doc/Documentation/device-mapper/verity.txt
31
Shikha Panwar414ea892022-10-12 13:45:52 +000032/// Device-Mapper’s “verity” target provides transparent integrity checking of block devices using
33/// a cryptographic digest provided by the kernel crypto API
34pub struct DmVerityTarget(Box<[u8]>);
35
36/// Version of the verity target spec.
Jiyong Park86c9b082021-06-04 19:03:48 +090037pub enum DmVerityVersion {
Shikha Panwar414ea892022-10-12 13:45:52 +000038 /// Only `1` is supported.
Jiyong Park86c9b082021-06-04 19:03:48 +090039 V1,
40}
41
42/// The hash algorithm to use. SHA256 and SHA512 are supported.
Jiyong Park99a35b82021-06-07 10:13:44 +090043#[allow(dead_code)]
Jiyong Park86c9b082021-06-04 19:03:48 +090044pub enum DmVerityHashAlgorithm {
Shikha Panwar414ea892022-10-12 13:45:52 +000045 /// sha with 256 bit hash
Jiyong Park86c9b082021-06-04 19:03:48 +090046 SHA256,
Shikha Panwar414ea892022-10-12 13:45:52 +000047 /// sha with 512 bit hash
Jiyong Park86c9b082021-06-04 19:03:48 +090048 SHA512,
49}
50
51/// A builder that constructs `DmVerityTarget` struct.
52pub 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
Jiyong Park86c9b082021-06-04 19:03:48 +090062impl DmVerityTarget {
Shikha Panwar414ea892022-10-12 13:45:52 +000063 /// flatten into slice
Jiyong Park3c327d22021-06-08 20:51:54 +090064 pub fn as_slice(&self) -> &[u8] {
Jiyong Park86c9b082021-06-04 19:03:48 +090065 self.0.as_ref()
66 }
67}
68
69impl<'a> Default for DmVerityTargetBuilder<'a> {
70 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
Nikita Ioffe5fb5ec02024-10-31 03:39:22 +000083const BLOCK_SIZE: u64 = 4096;
84
Jiyong Park86c9b082021-06-04 19:03:48 +090085impl<'a> DmVerityTargetBuilder<'a> {
86 /// Sets the device that will be used as the data device (i.e. providing actual data).
87 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.
94 pub fn hash_device(&mut self, p: &'a Path) -> &mut Self {
95 self.hash_device = Some(p);
96 self
97 }
98
Shikha Panwar414ea892022-10-12 13:45:52 +000099 /// Sets the hash algorithm that the merkle tree is using.
Jiyong Park86c9b082021-06-04 19:03:48 +0900100 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.
106 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.
113 pub fn salt(&mut self, salt: &'a [u8]) -> &mut Self {
114 self.salt = Some(salt);
115 self
116 }
117
118 /// Constructs a `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")?;
Nikita Ioffe5fb5ec02024-10-31 03:39:22 +0000136 let data_block_size = BLOCK_SIZE;
Jiyong Park86c9b082021-06-04 19:03:48 +0900137 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")?;
Nikita Ioffe5fb5ec02024-10-31 03:39:22 +0000145 let hash_block_size = BLOCK_SIZE;
Jiyong Park86c9b082021-06-04 19:03:48 +0900146
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 {
Alice Wang8ed29ce2023-12-01 08:33:12 +0000153 hex::encode(root_digest)
Jiyong Park86c9b082021-06-04 19:03:48 +0900154 } 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 {
Alice Wang8ed29ce2023-12-01 08:33:12 +0000161 hex::encode(self.salt.unwrap())
Jiyong Park86c9b082021-06-04 19:03:48 +0900162 };
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);
Frederick Mayle8f795902023-10-23 15:48:34 -0700197 buf.write_all(header.as_bytes())?;
Jiyong Park86c9b082021-06-04 19:03:48 +0900198 buf.write_all(body.as_bytes())?;
199 buf.write_all(vec![0; padding].as_slice())?;
200 Ok(DmVerityTarget(buf.into_boxed_slice()))
201 }
202}