1pub use fsverity_merkle::MerkleVerifier;
6use fsverity_merkle::{FsVerityHasher, FsVerityHasherOptions};
7use zx_status::Status;
8
9#[derive(thiserror::Error, Debug, PartialEq, Eq)]
10pub enum DmVerityError {
11 #[error("Unsupported hash algorithm: {0}")]
12 UnsupportedAlgorithm(String),
13 #[error("Root hash mismatch")]
14 RootHashMismatch,
15 #[error("Invalid verifier arguments")]
16 InvalidVerifierArgs,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum HashAlgorithm {
21 Sha256,
22 Sha512,
23}
24
25impl std::str::FromStr for HashAlgorithm {
26 type Err = DmVerityError;
27
28 fn from_str(s: &str) -> Result<Self, Self::Err> {
29 match s {
30 "sha256" => Ok(HashAlgorithm::Sha256),
31 "sha512" => Ok(HashAlgorithm::Sha512),
32 _ => Err(DmVerityError::UnsupportedAlgorithm(s.to_string())),
33 }
34 }
35}
36
37impl HashAlgorithm {
38 pub fn as_str(&self) -> &'static str {
39 match self {
40 HashAlgorithm::Sha256 => "sha256",
41 HashAlgorithm::Sha512 => "sha512",
42 }
43 }
44}
45
46#[derive(Debug, Clone, Default, PartialEq, Eq)]
53pub struct DmVerityTargetOptionalParams {
54 pub ignore_zero_blocks: bool,
58 pub restart_on_corruption: bool,
61}
62
63#[derive(Debug, Clone)]
70pub struct DmVerityTargetParams {
71 pub version: String,
73 pub block_device_path: String,
75 pub hash_device_path: String,
77 pub data_block_size: u64,
79 pub hash_block_size: u64,
81 pub num_data_blocks: u64,
83 pub hash_start_block: u64,
85 pub hash_algorithm: HashAlgorithm,
87 pub root_digest: String,
89 pub salt: String,
91 pub optional_params: DmVerityTargetOptionalParams,
93}
94
95pub fn create_verifier(
98 params: &DmVerityTargetParams,
99 leaf_hashes: Box<[u8]>,
100) -> Result<MerkleVerifier, DmVerityError> {
101 let salt_bytes = hex::decode(¶ms.salt).map_err(|_| DmVerityError::InvalidVerifierArgs)?;
102
103 let options = FsVerityHasherOptions::new_dmverity(salt_bytes, params.hash_block_size as usize);
104
105 let hasher = match params.hash_algorithm {
106 HashAlgorithm::Sha256 => FsVerityHasher::Sha256(options),
107 HashAlgorithm::Sha512 => FsVerityHasher::Sha512(options),
108 };
109
110 let expected_root_bytes =
111 hex::decode(¶ms.root_digest).map_err(|_| DmVerityError::InvalidVerifierArgs)?;
112
113 let verifier =
114 MerkleVerifier::new(&expected_root_bytes, leaf_hashes, hasher).map_err(|status| {
115 match status {
116 Status::IO_DATA_INTEGRITY => DmVerityError::RootHashMismatch,
117 _ => DmVerityError::InvalidVerifierArgs,
118 }
119 })?;
120
121 Ok(verifier)
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use fsverity_merkle::MerkleTree;
128 use std::str::FromStr;
129 use test_case::test_case;
130
131 const TEST_BLOCK_SIZE: u64 = 128;
132
133 fn create_verifier_test(
134 params: &DmVerityTargetParams,
135 leaf_hashes: &[u8],
136 ) -> Result<MerkleVerifier, DmVerityError> {
137 create_verifier(params, leaf_hashes.to_vec().into_boxed_slice())
138 }
139
140 fn create_data(size: usize) -> Vec<u8> {
141 let mut data = vec![0xABu8; size];
142 for (i, block) in data.chunks_mut(TEST_BLOCK_SIZE as usize).enumerate() {
143 let index_bytes = (i as u64).to_le_bytes();
144 let len = std::cmp::min(block.len(), index_bytes.len());
145 block[0..len].copy_from_slice(&index_bytes[0..len]);
146 }
147 data
148 }
149
150 fn build_test_verifier(
151 data: &[u8],
152 hash_algorithm: HashAlgorithm,
153 salt: &str,
154 ) -> (MerkleVerifier, DmVerityTargetParams) {
155 let salt_bytes = hex::decode(salt).expect("Failed to decode salt");
156 let hasher = match hash_algorithm {
157 HashAlgorithm::Sha256 => FsVerityHasher::Sha256(FsVerityHasherOptions::new_dmverity(
158 salt_bytes.clone(),
159 TEST_BLOCK_SIZE as usize,
160 )),
161 HashAlgorithm::Sha512 => FsVerityHasher::Sha512(FsVerityHasherOptions::new_dmverity(
162 salt_bytes.clone(),
163 TEST_BLOCK_SIZE as usize,
164 )),
165 };
166
167 let tree = MerkleTree::from_data(data, hasher);
168 let root_digest = hex::encode(tree.root());
169 let leaf_hashes = tree.leaf_hashes();
170
171 let params = DmVerityTargetParams {
172 version: "1".to_string(),
173 block_device_path: "mock".to_string(),
174 hash_device_path: "mock".to_string(),
175 data_block_size: TEST_BLOCK_SIZE,
176 hash_block_size: TEST_BLOCK_SIZE,
177 num_data_blocks: (data.len() as u64).div_ceil(TEST_BLOCK_SIZE),
178 hash_start_block: 0,
179 hash_algorithm,
180 root_digest,
181 salt: salt.to_string(),
182 optional_params: DmVerityTargetOptionalParams::default(),
183 };
184
185 let verifier =
186 create_verifier_test(¶ms, &leaf_hashes).expect("Failed to create verifier");
187 (verifier, params)
188 }
189
190 #[test_case(HashAlgorithm::Sha256; "sha256")]
191 #[test_case(HashAlgorithm::Sha512; "sha512")]
192 fn test_verify_read_success(hash_algorithm: HashAlgorithm) {
193 let data = create_data(512);
194 let (verifier, _) = build_test_verifier(&data, hash_algorithm, "aabbcc");
195
196 verifier.verify(0, &data).expect("Failed to verify read");
197 }
198
199 #[test_case(HashAlgorithm::Sha256; "sha256")]
200 #[test_case(HashAlgorithm::Sha512; "sha512")]
201 fn test_verify_read_corrupted_data(hash_algorithm: HashAlgorithm) {
202 let mut data = create_data(512);
203 let (verifier, _) = build_test_verifier(&data, hash_algorithm, "aabbcc");
204
205 data[0] ^= 0xFF;
207
208 verifier
209 .verify(0, &data)
210 .expect_err("Read verification should have failed for corrupted data");
211 }
212
213 #[test_case(HashAlgorithm::Sha256; "sha256")]
214 #[test_case(HashAlgorithm::Sha512; "sha512")]
215 fn test_verify_root_mismatch(hash_algorithm: HashAlgorithm) {
216 let data = create_data(512);
217 let salt = "aabbcc";
218 let salt_bytes = hex::decode(salt).expect("Failed to decode salt");
219 let hasher = match hash_algorithm {
220 HashAlgorithm::Sha256 => FsVerityHasher::Sha256(FsVerityHasherOptions::new_dmverity(
221 salt_bytes.clone(),
222 TEST_BLOCK_SIZE as usize,
223 )),
224 HashAlgorithm::Sha512 => FsVerityHasher::Sha512(FsVerityHasherOptions::new_dmverity(
225 salt_bytes.clone(),
226 TEST_BLOCK_SIZE as usize,
227 )),
228 };
229
230 let tree = MerkleTree::from_data(&data, hasher);
231 let leaf_hashes = tree.leaf_hashes();
232
233 let digest_size = match hash_algorithm {
234 HashAlgorithm::Sha256 => 32,
235 HashAlgorithm::Sha512 => 64,
236 };
237 let wrong_root_digest = "00".repeat(digest_size);
238
239 let params = DmVerityTargetParams {
240 version: "1".to_string(),
241 block_device_path: "mock".to_string(),
242 hash_device_path: "mock".to_string(),
243 data_block_size: TEST_BLOCK_SIZE,
244 hash_block_size: TEST_BLOCK_SIZE,
245 num_data_blocks: (data.len() as u64).div_ceil(TEST_BLOCK_SIZE),
246 hash_start_block: 0,
247 hash_algorithm,
248 root_digest: wrong_root_digest,
249 salt: salt.to_string(),
250 optional_params: DmVerityTargetOptionalParams::default(),
251 };
252
253 assert_eq!(
254 create_verifier_test(¶ms, &leaf_hashes)
255 .expect_err("create_verifier passed unexpectedly with root mismatch"),
256 DmVerityError::RootHashMismatch
257 );
258 }
259
260 #[test_case(HashAlgorithm::Sha256; "sha256")]
261 #[test_case(HashAlgorithm::Sha512; "sha512")]
262 fn test_verify_read_non_zero_offset(hash_algorithm: HashAlgorithm) {
263 let data = create_data(1024);
264 let (verifier, _) = build_test_verifier(&data, hash_algorithm, "aabbcc");
265
266 let block_index = 2;
267 let offset = block_index * TEST_BLOCK_SIZE as usize;
268 let block_data = &data[offset..(offset + TEST_BLOCK_SIZE as usize)];
269
270 verifier.verify(offset, block_data).expect("Failed to verify read at non-zero offset");
271 }
272
273 #[test_case(HashAlgorithm::Sha256; "sha256")]
274 #[test_case(HashAlgorithm::Sha512; "sha512")]
275 fn test_verify_read_non_zero_offset_corrupted(hash_algorithm: HashAlgorithm) {
276 let data = create_data(1024);
277 let (verifier, _) = build_test_verifier(&data, hash_algorithm, "aabbcc");
278
279 let block_index = 2;
280 let offset = block_index * TEST_BLOCK_SIZE as usize;
281
282 let mut block_data = data[offset..(offset + TEST_BLOCK_SIZE as usize)].to_vec();
283 block_data[0] ^= 0xFF;
284
285 verifier.verify(offset, &block_data).expect_err(
286 "Read verification should have failed for corrupted data at non-zero offset",
287 );
288 }
289
290 #[test]
291 fn test_invalid_root_digest() {
292 let data = create_data(512);
293 let (_, mut params) = build_test_verifier(&data, HashAlgorithm::Sha256, "aabbcc");
294 let leaf_hashes = vec![0u8; 32];
295
296 params.root_digest = "invalid_hex".to_string();
298 assert_eq!(
299 create_verifier_test(¶ms, &leaf_hashes)
300 .expect_err("create_verifier passed unexpectedly with invalid root digest"),
301 DmVerityError::InvalidVerifierArgs
302 );
303
304 params.root_digest = "a".to_string();
306 assert_eq!(
307 create_verifier_test(¶ms, &leaf_hashes)
308 .expect_err("create_verifier passed unexpectedly with odd-length root digest"),
309 DmVerityError::InvalidVerifierArgs
310 );
311
312 params.root_digest = "00".repeat(31);
314 assert_eq!(
315 create_verifier_test(¶ms, &leaf_hashes)
316 .expect_err("create_verifier passed unexpectedly with wrong-sized root digest"),
317 DmVerityError::InvalidVerifierArgs
318 );
319 }
320
321 #[test]
322 fn test_invalid_salt() {
323 let data = create_data(512);
324 let (_, mut params) = build_test_verifier(&data, HashAlgorithm::Sha256, "aabbcc");
325 let leaf_hashes = vec![0u8; 32];
326
327 params.salt = "invalid_hex".to_string();
329 assert_eq!(
330 create_verifier_test(¶ms, &leaf_hashes)
331 .expect_err("create_verifier passed unexpectedly with invalid salt"),
332 DmVerityError::InvalidVerifierArgs
333 );
334
335 params.salt = "a".to_string();
337 assert_eq!(
338 create_verifier_test(¶ms, &leaf_hashes)
339 .expect_err("create_verifier passed unexpectedly with odd-length salt"),
340 DmVerityError::InvalidVerifierArgs
341 );
342 }
343
344 #[test]
345 fn test_invalid_leaf_hashes_length() {
346 let data = create_data(512);
347 let (_, params) = build_test_verifier(&data, HashAlgorithm::Sha256, "aabbcc");
348
349 let bad_leaf_hashes = vec![0u8; 31];
351 assert_eq!(
352 create_verifier_test(¶ms, &bad_leaf_hashes)
353 .expect_err("create_verifier passed unexpectedly with short leaf hashes"),
354 DmVerityError::InvalidVerifierArgs
355 );
356
357 let bad_leaf_hashes = vec![0u8; 33];
358 assert_eq!(
359 create_verifier_test(¶ms, &bad_leaf_hashes)
360 .expect_err("create_verifier passed unexpectedly with long leaf hashes"),
361 DmVerityError::InvalidVerifierArgs
362 );
363 }
364
365 #[test]
366 fn test_unsupported_algorithm() {
367 assert_eq!(
368 HashAlgorithm::from_str("md5")
369 .expect_err("HashAlgorithm::from_str passed unexpectedly for md5"),
370 DmVerityError::UnsupportedAlgorithm("md5".to_string())
371 );
372 HashAlgorithm::from_str("sha256")
373 .expect("HashAlgorithm::from_str failed unexpectedly for sha256");
374 HashAlgorithm::from_str("sha512")
375 .expect("HashAlgorithm::from_str failed unexpectedly for sha512");
376 }
377}