Never crash: crypt has no panic paths
AES blocks come from `as_chunks`, the IV from `split_first_chunk`, and the R6 hash takes its AES key and IV with `first_chunk`, so no `expect` is needed. The crate now denies the panic lints. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
8f1f4e7ffb
commit
95d8ba3101
|
|
@ -13,9 +13,8 @@ enum Cipher {
|
|||
|
||||
impl Cipher {
|
||||
fn new(key: &[u8]) -> Self {
|
||||
if key.len() >= 32 {
|
||||
let k: [u8; 32] = key[..32].try_into().expect("32 bytes");
|
||||
Cipher::A256(Box::new(Aes256::new(&Array::from(k))))
|
||||
if let Some(k) = key.first_chunk::<32>() {
|
||||
Cipher::A256(Box::new(Aes256::new(&Array::from(*k))))
|
||||
} else {
|
||||
let mut k = [0u8; 16];
|
||||
let n = key.len().min(16);
|
||||
|
|
@ -55,14 +54,12 @@ pub fn aes_cbc_encrypt(key: &[u8], iv: &[u8; 16], data: &[u8], pad: bool) -> Vec
|
|||
buf.resize(buf.len().div_ceil(16) * 16, 0);
|
||||
}
|
||||
let mut prev = *iv;
|
||||
for chunk in buf.chunks_mut(16) {
|
||||
let mut b: [u8; 16] = chunk.try_into().expect("16 bytes");
|
||||
for b in buf.as_chunks_mut::<16>().0 {
|
||||
for (x, p) in b.iter_mut().zip(prev) {
|
||||
*x ^= p;
|
||||
}
|
||||
c.encrypt(&mut b);
|
||||
chunk.copy_from_slice(&b);
|
||||
prev = b;
|
||||
c.encrypt(b);
|
||||
prev = *b;
|
||||
}
|
||||
buf
|
||||
}
|
||||
|
|
@ -70,11 +67,10 @@ pub fn aes_cbc_encrypt(key: &[u8], iv: &[u8; 16], data: &[u8], pad: bool) -> Vec
|
|||
/// Decrypt; with `pad`, PKCS#5 padding is removed when valid.
|
||||
pub fn aes_cbc_decrypt(key: &[u8], iv: &[u8; 16], data: &[u8], pad: bool) -> Vec<u8> {
|
||||
let c = Cipher::new(key);
|
||||
let whole = data.len() / 16 * 16;
|
||||
let mut out = Vec::with_capacity(whole);
|
||||
let blocks = data.as_chunks::<16>().0;
|
||||
let mut out = Vec::with_capacity(blocks.len() * 16);
|
||||
let mut prev = *iv;
|
||||
for chunk in data[..whole].chunks(16) {
|
||||
let cipher: [u8; 16] = chunk.try_into().expect("16 bytes");
|
||||
for &cipher in blocks {
|
||||
let mut b = cipher;
|
||||
c.decrypt(&mut b);
|
||||
for (x, p) in b.iter_mut().zip(prev) {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
//! The crate knows nothing about the COS object model. Callers pass in the values of the
|
||||
//! `/Encrypt` dictionary and the first element of the trailer `/ID`.
|
||||
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||||
|
||||
mod aes_cbc;
|
||||
mod rc4;
|
||||
|
||||
|
|
@ -256,16 +258,19 @@ impl SecurityHandler {
|
|||
h.update(num.to_le_bytes());
|
||||
h.update(generation.to_le_bytes());
|
||||
h.update(data);
|
||||
let iv: [u8; 16] = h.finalize()[..16].try_into().expect("16 bytes");
|
||||
let digest = h.finalize();
|
||||
let mut iv = [0u8; 16];
|
||||
for (d, s) in iv.iter_mut().zip(digest.iter()) {
|
||||
*d = *s;
|
||||
}
|
||||
let mut out = iv.to_vec();
|
||||
out.extend(aes_cbc_encrypt(&key, &iv, data, true));
|
||||
out
|
||||
} else {
|
||||
if data.len() < 16 {
|
||||
let Some((iv, body)) = data.split_first_chunk::<16>() else {
|
||||
return Vec::new(); // only (part of) an IV: empty plaintext
|
||||
}
|
||||
let (iv, body) = data.split_at(16);
|
||||
aes_cbc_decrypt(&key, iv.try_into().expect("16 bytes"), body, true)
|
||||
};
|
||||
aes_cbc_decrypt(&key, iv, body, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -429,15 +434,18 @@ fn hash_r6(r: i64, pw: &[u8], salt: &[u8], udata: &[u8]) -> Vec<u8> {
|
|||
k1.extend_from_slice(&k);
|
||||
k1.extend_from_slice(udata);
|
||||
}
|
||||
let e = aes_cbc_encrypt(&k[..16], k[16..32].try_into().expect("16 bytes"), &k1, false);
|
||||
let m = e[..16].iter().map(|b| u32::from(*b)).sum::<u32>() % 3;
|
||||
// `k` is always a SHA-2 digest (32, 48 or 64 bytes): key = its first 16 bytes, IV = the next 16.
|
||||
let Some((key, rest)) = k.split_first_chunk::<16>() else { break };
|
||||
let Some(iv) = rest.first_chunk::<16>() else { break };
|
||||
let e = aes_cbc_encrypt(key, iv, &k1, false);
|
||||
let m = e.iter().take(16).map(|b| u32::from(*b)).sum::<u32>() % 3;
|
||||
k = match m {
|
||||
0 => Sha256::digest(&e).to_vec(),
|
||||
1 => Sha384::digest(&e).to_vec(),
|
||||
_ => Sha512::digest(&e).to_vec(),
|
||||
};
|
||||
round += 1;
|
||||
if round >= 64 && u32::from(*e.last().expect("non-empty")) <= round - 32 {
|
||||
if round >= 64 && e.last().is_none_or(|b| u32::from(*b) <= round - 32) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue