Files
boxmaker/crates/proto/src/sha1.rs
T
kyleandClaude Opus 5.5 0081f24b70 gatewayd: strict status lines and chunk lines; no as casts (M4a review, findings 1 and 3)
A status line splits on single spaces only, and every chunk line must end in CRLF. The bounded
`as` casts in http.rs, handshake.rs and proto's sha1.rs become try_from and from.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 01:23:51 -07:00

142 lines
4.7 KiB
Rust

//! SHA-1 (FIPS 180-4), used only to check the `Sec-WebSocket-Accept` header of a WebSocket
//! handshake (RFC 6455, section 4.2.2). Never use it for anything that needs to resist attack.
/// The digest of `data`.
pub fn sha1(data: &[u8]) -> [u8; 20] {
let mut h = Sha1::new();
h.update(data);
h.finish()
}
/// SHA-1 fed in pieces.
#[derive(Debug, Clone)]
pub struct Sha1 {
state: [u32; 5],
block: [u8; 64],
filled: usize,
length: u64,
}
impl Default for Sha1 {
fn default() -> Self {
Self::new()
}
}
impl Sha1 {
pub fn new() -> Sha1 {
Sha1 {
state: [
0x6745_2301,
0xEFCD_AB89,
0x98BA_DCFE,
0x1032_5476,
0xC3D2_E1F0,
],
block: [0; 64],
filled: 0,
length: 0,
}
}
pub fn update(&mut self, mut data: &[u8]) {
// Add 8 * data.len() to `length` (wrapping; `u64::try_from`, never `as`). Copy bytes into
// `block` from `filled` on; each time it is full (64), `compress` it and set `filled` to 0.
// Use `split_at` and `get_mut(..)`, no indexing that can go out of bounds.
self.length = self
.length
.wrapping_add(8u64.wrapping_mul(u64::try_from(data.len()).unwrap_or(u64::MAX)));
while !data.is_empty() {
let space = 64 - self.filled;
let take = data.len().min(space);
let (left, right) = data.split_at(take);
if let Some(dst) = self.block.get_mut(self.filled..self.filled + take) {
dst.copy_from_slice(left);
}
self.filled += take;
if self.filled == 64 {
let block = self.block;
self.compress(&block);
self.filled = 0;
}
data = right;
}
}
pub fn finish(mut self) -> [u8; 20] {
// Save `length`. Feed 0x80 then zeros so that 56 bytes of the block are filled (56 -
// filled, or 120 - filled when filled >= 56), then the saved length as 8 big-endian bytes,
// through `update`. `update` adds to `length`: put the saved value back after. Then the
// five state words, big-endian.
let saved = self.length;
let zeros = if self.filled < 56 {
56 - self.filled
} else {
120 - self.filled
};
let total = zeros + 8;
let mut pad = [0u8; 128];
pad[0] = 0x80;
pad[zeros..total].copy_from_slice(&saved.to_be_bytes());
self.update(&pad[..total]);
self.length = saved;
let mut out = [0u8; 20];
for (i, word) in self.state.iter().enumerate() {
out[i * 4..i * 4 + 4].copy_from_slice(&word.to_be_bytes());
}
out
}
fn compress(&mut self, block: &[u8; 64]) {
// FIPS 180-4, section 6.1.2: w[0..16] are the block as big-endian u32s; w[i] = (w[i-3] ^
// w[i-8] ^ w[i-14] ^ w[i-16]).rotate_left(1) for 16..80. Eighty rounds with f and k by
// range: 0..=19 (b & c) | (!b & d), 0x5A827999; 20..=39 b ^ c ^ d, 0x6ED9EBA1; 40..=59 (b &
// c) | (b & d) | (c & d), 0x8F1BBCDC; 60..=79 b ^ c ^ d, 0xCA62C1D6. All additions
// wrapping. Add a..e into state.
let mut w: [u32; 80] = [0; 80];
let (chunks, _) = block.as_chunks::<4>();
for (i, chunk) in chunks.iter().enumerate() {
w[i] = u32::from_be_bytes(*chunk);
}
for i in 16..80 {
w[i] = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]).rotate_left(1);
}
let (mut a, mut b, mut c, mut d, mut e) = (
self.state[0],
self.state[1],
self.state[2],
self.state[3],
self.state[4],
);
for (round, &ww) in w.iter().enumerate() {
let (f, k) = if round <= 19 {
((b & c) | (!b & d), 0x5A82_7999)
} else if round <= 39 {
(b ^ c ^ d, 0x6ED9_EBA1)
} else if round <= 59 {
((b & c) | (b & d) | (c & d), 0x8F1B_BCDC)
} else {
(b ^ c ^ d, 0xCA62_C1D6)
};
let temp = a
.rotate_left(5)
.wrapping_add(f)
.wrapping_add(e)
.wrapping_add(k);
e = d;
d = c;
c = b.rotate_left(30);
b = a;
a = temp.wrapping_add(ww);
}
self.state[0] = self.state[0].wrapping_add(a);
self.state[1] = self.state[1].wrapping_add(b);
self.state[2] = self.state[2].wrapping_add(c);
self.state[3] = self.state[3].wrapping_add(d);
self.state[4] = self.state[4].wrapping_add(e);
}
}