hayro-syntax: refuse a legacy encryption key length above 128 bits instead of panicking (#817)

Port of pdfcraft #1882 (344ba9f8). Revisions 2-4 take the file key from a 16-byte MD5 hash; /V 2 /R 3 /Length 256 sliced past it and panicked on open.
This commit is contained in:
Dylan M. Taylor authored and GitHub committed 2026-11-11 15:46:28 -05:00
1 parent cc6106c56a
commit 9eee29b69d
3 files changed
+23 -2

No files matched your search

+7
View File
@@ -377,3 +377,10 @@ comments label them `Craft patch (flatten)`.
| hayro-interpret `color.rs` | `color::InkSpace`, `ColorSpace::ink_space` and `ColorSpace::ink_values`: how a space's colors are kept as inks (gray, CMYK, its colorants, `All`, `None`) and colors converted in batches, process color as `Color::ink_paint` converts it. `ColorSpace::components`; `ColorSpace::is_device_cmyk` and `ColorSpace::spot_alternate` public |
| hayro-interpret `types.rs`, `x_object.rs` | `RasterImage::with_inks`: an image's samples in its own space's inks (`InkImage`), `/Decode` and `/Matte` applied, transfer functions left out, with its alpha at the alpha's own size |
| hayro-interpret `interpret/mod.rs` | The `sh` operator drops the shading's `/Background`, which only a shading pattern paints (ISO 32000-2 §8.7.4.5.1) |
Legacy encryption key length, ported from [PdfCraft #1882](https://github.com/storytold/pdfcraft/pull/1882)
(commit 344ba9f8, marked `PdfCraft patch` in `hayro-syntax/src/crypto/mod.rs`): an encryption
dictionary of revision 2–4 whose `/Length` is above 128 bits is refused with `InvalidEncryption`.
Those revisions take the file key from a 16-byte MD5 hash, so `/V 2 /R 3 /Length 256` sliced past
the hash and panicked on open. Test:
`crypto::pdfcraft_password_tests::legacy_key_length_above_128_bits_is_refused_not_a_panic`.
+1 -1
View File
@@ -98,7 +98,7 @@
"path": "src/crypto/mod.rs",
"source_sha256": "6fca01cee7e67f3aaf435101b7ea98fa4fe23f807eadeaec63ee1c2ece759189",
"source_git_blob": "15371514c10dc993b335edfec19a3f989b497954",
"sha256": "6fca01cee7e67f3aaf435101b7ea98fa4fe23f807eadeaec63ee1c2ece759189"
"sha256": "9e0fc0edf48217b01acfcf7bcd3ebe0673f225f0c3857bf887efd0b0f31a74b2"
},
{
"path": "src/crypto/rc4.rs",
+15 -1
View File
@@ -145,7 +145,9 @@ pub(crate) fn get(
};
let byte_length = length / 8;
if byte_length == 0 {
// PdfCraft patch: revisions 2-4 derive an RC4 or AES-128 key from a 16-byte MD5 hash, so
// a longer /Length is malformed (and would slice past the end of the hash).
if byte_length == 0 || (revision <= 4 && byte_length > 16) {
return Err(InvalidEncryption);
}
@@ -902,4 +904,16 @@ mod pdfcraft_password_tests {
assert_eq!(unlock(&user, "cafe\u{301}"), ok, "R6");
assert_eq!(unlock(&user, "cafe"), wrong, "R6");
}
#[test]
#[cfg(feature = "std")]
fn legacy_key_length_above_128_bits_is_refused_not_a_panic() {
use crate::reader::{Reader, ReaderContext, ReaderExt};
let hex32 = "00".repeat(32);
for (length, expected) in [(128, DecryptionError::PasswordProtected), (256, InvalidEncryption)] {
let dict = format!("<< /Filter /Standard /V 2 /R 3 /Length {length} /P -4 /O <{hex32}> /U <{hex32}> >>");
let dict = Reader::new(dict.as_bytes()).read_with_context::<Dict<'_>>(&ReaderContext::dummy());
assert_eq!(get(&dict.unwrap(), b"", b"").err(), Some(expected), "/Length {length}");
}
}
}