use std::cell::Cell;
use pdfrum_crypt::{CryptClass, Iv, SecurityHandler};
use pdfrum_object::ObjRef;
use sha2::{Digest, Sha256};
use zeroize::Zeroize;
use crate::Error;
pub struct IvSource {
secret: [u8; 32],
counter: Cell<u64>,
}
impl std::fmt::Debug for IvSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("IvSource")
.field("counter", &self.counter.get())
.finish_non_exhaustive()
}
}
impl Drop for IvSource {
fn drop(&mut self) {
self.secret.zeroize();
}
}
impl IvSource {
pub fn from_os() -> Result<Self, Error> {
let mut secret = [0u8; 32];
getrandom::fill(&mut secret).map_err(|_| Error::NoEntropy)?;
Ok(Self {
secret,
counter: Cell::new(0),
})
}
fn next(&self, obj: ObjRef) -> Iv {
let index = self.counter.get();
self.counter.set(index.wrapping_add(1));
let mut hash = Sha256::new();
hash.update(self.secret);
hash.update(index.to_le_bytes());
hash.update(obj.num.to_le_bytes());
hash.update(obj.generation.to_le_bytes());
let digest = hash.finalize();
let mut out = [0u8; 16];
for (slot, byte) in out.iter_mut().zip(digest) {
*slot = byte;
}
Iv(out)
}
}
#[derive(Debug, Clone, Copy)]
pub struct Encryptor<'a> {
handler: &'a SecurityHandler,
ivs: &'a IvSource,
object_number: u32,
}
impl<'a> Encryptor<'a> {
#[must_use]
pub const fn new(handler: &'a SecurityHandler, ivs: &'a IvSource, object_number: u32) -> Self {
Self {
handler,
ivs,
object_number,
}
}
#[must_use]
pub fn encrypts_metadata(&self) -> bool {
self.handler.encrypt_metadata()
}
#[must_use]
pub fn encrypt(&self, class: CryptClass, data: &[u8]) -> Vec<u8> {
let obj = ObjRef::new(self.object_number, 0);
self.handler.encrypt(obj, class, self.ivs.next(obj), data)
}
}
#[derive(Debug)]
pub(crate) struct Security<'a> {
pub(crate) handler: &'a SecurityHandler,
pub(crate) ivs: IvSource,
pub(crate) encrypt_object: Option<u32>,
}
impl Security<'_> {
#[must_use]
pub(crate) fn for_object(&self, num: u32) -> Option<Encryptor<'_>> {
if self.encrypt_object == Some(num) {
return None;
}
Some(Encryptor::new(self.handler, &self.ivs, num))
}
}
#[cfg(test)]
mod tests {
use super::{Encryptor, IvSource, Security};
use pdfrum_crypt::{CryptClass, SecurityHandler};
use pdfrum_object::ObjRef;
fn handler() -> SecurityHandler {
SecurityHandler::AesV5 {
key: Box::new([0x42; 32]),
revision: 6,
permissions: 0xFFFF_FFFC,
owner_unlocked: false,
encrypt_metadata: true,
encoding: pdfrum_crypt::PasswordEncoding::AsGiven,
embedded_cipher: None,
strings_identity: false,
}
}
#[test]
fn vectors_never_repeat_within_one_save() {
let ivs = IvSource::from_os().unwrap();
let mut seen = std::collections::BTreeSet::new();
for num in 1..40u32 {
for _ in 0..4 {
assert!(seen.insert(ivs.next(ObjRef::new(num, 0)).0), "{num}");
}
}
assert_eq!(seen.len(), 39 * 4);
}
#[test]
fn two_sources_never_agree() {
let a = IvSource::from_os().unwrap();
let b = IvSource::from_os().unwrap();
for num in 1..8u32 {
assert_ne!(a.next(ObjRef::new(num, 0)), b.next(ObjRef::new(num, 0)));
}
}
#[test]
fn an_encryptor_round_trips_under_its_object_number() {
let h = handler();
let ivs = IvSource::from_os().unwrap();
let enc = Encryptor::new(&h, &ivs, 12);
let payload = b"the quick brown fox".to_vec();
let sealed = enc.encrypt(CryptClass::String, &payload);
assert_ne!(sealed, payload);
assert_eq!(
h.decrypt(ObjRef::new(12, 0), CryptClass::String, &sealed),
payload
);
}
#[test]
fn the_encrypt_dictionary_is_never_given_an_encryptor() {
let h = handler();
let security = Security {
handler: &h,
ivs: IvSource::from_os().unwrap(),
encrypt_object: Some(9),
};
assert!(security.for_object(9).is_none());
assert!(security.for_object(8).is_some());
assert!(security.for_object(10).is_some());
let security = Security {
handler: &h,
ivs: IvSource::from_os().unwrap(),
encrypt_object: None,
};
assert!(security.for_object(9).is_some());
}
#[test]
fn the_identity_handler_writes_what_it_was_given() {
let h = SecurityHandler::Identity;
let ivs = IvSource::from_os().unwrap();
let enc = Encryptor::new(&h, &ivs, 4);
let payload = vec![0xABu8; 33];
assert_eq!(enc.encrypt(CryptClass::Stream, &payload), payload);
}
}