composefs/fsverity/
digest.rs

1//! Userspace fs-verity digest computation.
2//!
3//! This module implements the fs-verity Merkle tree algorithm in userspace,
4//! allowing computation of fs-verity digests without kernel support.
5
6use core::{cmp::min, mem::size_of};
7
8use sha2::Digest;
9
10use super::FsVerityHashValue;
11
12#[derive(Debug)]
13struct FsVerityLayer<H: FsVerityHashValue, const LG_BLKSZ: u8 = 12> {
14    context: H::Digest,
15    remaining: usize,
16}
17
18impl<H: FsVerityHashValue, const LG_BLKSZ: u8> FsVerityLayer<H, LG_BLKSZ> {
19    fn new() -> Self {
20        Self {
21            context: H::Digest::new(),
22            remaining: 1 << LG_BLKSZ,
23        }
24    }
25
26    fn add_data(&mut self, data: &[u8]) {
27        self.context.update(data);
28        self.remaining -= data.len();
29    }
30
31    fn complete(&mut self) -> H {
32        self.context.update([0].repeat(self.remaining));
33        self.remaining = 1 << LG_BLKSZ;
34        self.context.finalize_reset().into()
35    }
36}
37
38/// Incremental fs-verity digest computation.
39///
40/// This hasher allows computing fs-verity digests incrementally by feeding
41/// data in chunks. The data must be provided in block-aligned chunks (4KB by default)
42/// except for the final chunk which may be smaller.
43///
44/// # Example
45/// ```ignore
46/// use composefs::fsverity::{FsVerityHasher, Sha256HashValue};
47///
48/// let mut hasher = FsVerityHasher::<Sha256HashValue>::new();
49/// hasher.write_all(b"hello world");
50/// let digest = hasher.digest();
51/// ```
52#[derive(Debug)]
53pub struct FsVerityHasher<H: FsVerityHashValue, const LG_BLKSZ: u8 = 12> {
54    layers: Vec<FsVerityLayer<H, LG_BLKSZ>>,
55    value: Option<H>,
56    n_bytes: u64,
57}
58
59impl<H: FsVerityHashValue, const LG_BLKSZ: u8> FsVerityHasher<H, LG_BLKSZ> {
60    /// The block size in bytes used for fs-verity Merkle tree computation.
61    pub const BLOCK_SIZE: usize = 1 << LG_BLKSZ;
62
63    /// Hash a complete buffer and return the fs-verity digest.
64    pub fn hash(buffer: &[u8]) -> H {
65        let mut hasher = Self::new();
66
67        let mut start = 0;
68        while start < buffer.len() {
69            let end = min(start + Self::BLOCK_SIZE, buffer.len());
70            hasher.add_block(&buffer[start..end]);
71            start = end;
72        }
73
74        hasher.digest()
75    }
76
77    /// Create a new incremental fs-verity hasher.
78    pub fn new() -> Self {
79        Self {
80            layers: vec![],
81            value: None,
82            n_bytes: 0,
83        }
84    }
85
86    /// Add a block of data to the hasher.
87    ///
88    /// For correct results, data should be provided in block-sized chunks (4KB)
89    /// except for the final chunk which may be smaller.
90    pub fn add_block(&mut self, data: &[u8]) {
91        if let Some(value) = self.value.take() {
92            // We had a complete value, but now we're adding new data.
93            // This means that we need to add a new hash layer...
94            let mut new_layer = FsVerityLayer::new();
95            new_layer.add_data(value.as_bytes());
96            self.layers.push(new_layer);
97        }
98
99        // Get the value of this block
100        let mut context = FsVerityLayer::<H, LG_BLKSZ>::new();
101        context.add_data(data);
102        let mut value = context.complete();
103        self.n_bytes += data.len() as u64;
104
105        for layer in self.layers.iter_mut() {
106            // We have a layer we need to hash this value into
107            layer.add_data(value.as_bytes());
108            if layer.remaining != 0 {
109                return;
110            }
111            // ...but now this layer itself is now complete, so get the value of *it*.
112            value = layer.complete();
113        }
114
115        // If we made it this far, we completed the last layer and have a value.  Store it.
116        self.value = Some(value);
117    }
118
119    fn root_hash(&mut self) -> H {
120        if let Some(value) = &self.value {
121            value.clone()
122        } else {
123            let mut value = H::EMPTY;
124
125            for layer in self.layers.iter_mut() {
126                // We have a layer we need to hash this value into
127                if value != H::EMPTY {
128                    layer.add_data(value.as_bytes());
129                }
130                if layer.remaining != (1 << LG_BLKSZ) {
131                    // ...but now this layer itself is complete, so get the value of *it*.
132                    value = layer.complete();
133                } else {
134                    value = H::EMPTY;
135                }
136            }
137
138            self.value = Some(value.clone());
139
140            value
141        }
142    }
143
144    /// Finalize and return the fs-verity digest.
145    ///
146    /// This consumes any remaining partial data and computes the final digest.
147    pub fn digest(&mut self) -> H {
148        /*
149        let mut root_hash = [0u8; 64];
150        let result = self.root_hash();
151        root_hash[..result.as_ref().len()].copy_from_slice(result.as_ref());
152
153        let descriptor = FsVerityDescriptor {
154            version: 1,
155            hash_algorithm: H::ALGORITHM,
156            log_blocksize: LG_BLKSZ,
157            salt_size: 0,
158            reserved_0x04: U32::new(0),
159            data_size: U64::new(self.n_bytes),
160            root_hash,
161            salt: [0; 32],
162            reserved: [0; 144],
163        };
164
165        let mut context = H::Digest::new();
166        context.update(descriptor.as_bytes());
167        context.finalize().into()
168            */
169
170        let mut context = H::Digest::new();
171        context.update(1u8.to_le_bytes()); /* version */
172        context.update(H::ALGORITHM.to_le_bytes()); /* hash_algorithm */
173        context.update(LG_BLKSZ.to_le_bytes()); /* log_blocksize */
174        context.update(0u8.to_le_bytes()); /* salt_size */
175        context.update([0; 4]); /* reserved */
176        context.update(self.n_bytes.to_le_bytes());
177        context.update(self.root_hash().as_bytes());
178        context.update([0].repeat(64 - size_of::<H>()));
179        context.update([0; 32]); /* salt */
180        context.update([0; 144]); /* reserved */
181        context.finalize().into()
182    }
183}
184
185impl<H: FsVerityHashValue, const LG_BLKSZ: u8> Default for FsVerityHasher<H, LG_BLKSZ> {
186    fn default() -> Self {
187        Self::new()
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use similar_asserts::assert_eq;
194
195    use crate::fsverity::{Sha256HashValue, Sha512HashValue};
196
197    use super::*;
198
199    #[test]
200    fn test_digest() {
201        assert_eq!(
202            FsVerityHasher::<Sha256HashValue, 12>::hash(b"hello world").to_hex(),
203            "1e2eaa4202d750a41174ee454970b92c1bc2f925b1e35076d8c7d5f56362ba64"
204        );
205
206        assert_eq!(
207            FsVerityHasher::<Sha512HashValue, 12>::hash(b"hello world").to_hex(),
208            "18430270729d162d4e469daca123ae61893db4b0583d8f7081e3bf4f92b88ba514e7982f10733fb6aa895195c5ae8fd2eb2c47a8be05513ce5a0c51a6f570409"
209        );
210    }
211}