|
1 | | -//! Dependency-free SHA-256 used by module artifact verification. |
| 1 | +//! SHA-256 used by module artifact verification. |
2 | 2 | //! |
3 | 3 | //! Verification at load is what an attestation later stands on: a recipient |
4 | 4 | //! earns the right to be handed a confidential body by having its artifact |
5 | 5 | //! digested here and matched against the operator's allowlist. Compiled |
6 | 6 | //! unconditionally rather than behind `modules`, because `module::sha256_file` |
7 | 7 | //! offers the same digest to callers that publish an asset without ever |
8 | 8 | //! loading one. |
| 9 | +//! |
| 10 | +//! Input is read in large chunks, not one 64-byte block per `read` syscall, and |
| 11 | +//! the compression function is `sha2`'s, which uses the CPU's SHA extensions |
| 12 | +//! where it has them, so hashing a 25 MB library stays off the startup path. |
| 13 | +//! Hashing the same library twice is avoided one level up, in |
| 14 | +//! `remembered_hash`. |
9 | 15 |
|
10 | 16 | use std::io::{self, Read}; |
11 | 17 |
|
12 | | -const INITIAL: [u32; 8] = [ |
13 | | - 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, |
14 | | -]; |
| 18 | +use sha2::{Digest, Sha256}; |
15 | 19 |
|
16 | | -const K: [u32; 64] = [ |
17 | | - 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, |
18 | | - 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, |
19 | | - 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, |
20 | | - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, |
21 | | - 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, |
22 | | - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, |
23 | | - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, |
24 | | - 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, |
25 | | -]; |
| 20 | +/// Bytes requested per `read`. Large enough that a 25 MB library is ~100 |
| 21 | +/// syscalls, small enough to stay out of the allocator's way. |
| 22 | +const CHUNK: usize = 256 * 1024; |
26 | 23 |
|
| 24 | +/// Lowercase hex SHA-256 of everything `reader` yields. |
27 | 25 | pub(crate) fn file_hex(mut reader: impl Read) -> io::Result<String> { |
28 | | - let mut state = INITIAL; |
29 | | - let mut block = [0u8; 64]; |
30 | | - let mut used = 0usize; |
31 | | - let mut byte_len = 0u64; |
| 26 | + let mut hasher = Sha256::new(); |
| 27 | + let mut chunk = vec![0u8; CHUNK]; |
32 | 28 | loop { |
33 | | - let read = reader.read(&mut block[used..])?; |
34 | | - if read == 0 { |
35 | | - break; |
36 | | - } |
37 | | - used += read; |
38 | | - byte_len = byte_len.wrapping_add(read as u64); |
39 | | - if used == block.len() { |
40 | | - compress(&mut state, &block); |
41 | | - used = 0; |
| 29 | + match reader.read(&mut chunk) { |
| 30 | + Ok(0) => break, |
| 31 | + Ok(read) => hasher.update(&chunk[..read]), |
| 32 | + Err(error) if error.kind() == io::ErrorKind::Interrupted => {} |
| 33 | + Err(error) => return Err(error), |
42 | 34 | } |
43 | 35 | } |
44 | | - block[used] = 0x80; |
45 | | - used += 1; |
46 | | - if used > 56 { |
47 | | - block[used..].fill(0); |
48 | | - compress(&mut state, &block); |
49 | | - block.fill(0); |
50 | | - } else { |
51 | | - block[used..56].fill(0); |
52 | | - } |
53 | | - block[56..].copy_from_slice(&byte_len.wrapping_mul(8).to_be_bytes()); |
54 | | - compress(&mut state, &block); |
55 | | - Ok(state.iter().map(|word| format!("{word:08x}")).collect()) |
56 | | -} |
57 | | - |
58 | | -fn compress(state: &mut [u32; 8], block: &[u8; 64]) { |
59 | | - let mut words = [0u32; 64]; |
60 | | - for (index, chunk) in block.as_chunks::<4>().0.iter().enumerate() { |
61 | | - words[index] = u32::from_be_bytes(*chunk); |
62 | | - } |
63 | | - for index in 16..64 { |
64 | | - let s0 = words[index - 15].rotate_right(7) |
65 | | - ^ words[index - 15].rotate_right(18) |
66 | | - ^ (words[index - 15] >> 3); |
67 | | - let s1 = words[index - 2].rotate_right(17) |
68 | | - ^ words[index - 2].rotate_right(19) |
69 | | - ^ (words[index - 2] >> 10); |
70 | | - words[index] = words[index - 16] |
71 | | - .wrapping_add(s0) |
72 | | - .wrapping_add(words[index - 7]) |
73 | | - .wrapping_add(s1); |
74 | | - } |
75 | | - |
76 | | - let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h] = *state; |
77 | | - for index in 0..64 { |
78 | | - let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25); |
79 | | - let choice = (e & f) ^ ((!e) & g); |
80 | | - let t1 = h |
81 | | - .wrapping_add(s1) |
82 | | - .wrapping_add(choice) |
83 | | - .wrapping_add(K[index]) |
84 | | - .wrapping_add(words[index]); |
85 | | - let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22); |
86 | | - let majority = (a & b) ^ (a & c) ^ (b & c); |
87 | | - let t2 = s0.wrapping_add(majority); |
88 | | - h = g; |
89 | | - g = f; |
90 | | - f = e; |
91 | | - e = d.wrapping_add(t1); |
92 | | - d = c; |
93 | | - c = b; |
94 | | - b = a; |
95 | | - a = t1.wrapping_add(t2); |
96 | | - } |
97 | | - for (slot, value) in state.iter_mut().zip([a, b, c, d, e, f, g, h]) { |
98 | | - *slot = slot.wrapping_add(value); |
99 | | - } |
| 36 | + Ok(hasher |
| 37 | + .finalize() |
| 38 | + .iter() |
| 39 | + .map(|byte| format!("{byte:02x}")) |
| 40 | + .collect()) |
100 | 41 | } |
101 | 42 |
|
102 | 43 | #[cfg(test)] |
|
0 commit comments