use aes::cipher::{BlockCipherEncrypt, KeyInit};
use aes::{Aes128, Aes256};
use cbc::cipher::KeyIvInit;
use md5::Md5;
use sha2::Digest as _;
use sha2::Sha256;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum EncryptionAlgorithm {
#[default]
Rc4128,
Aes256,
}
#[derive(Debug, Clone, Copy)]
pub struct PdfPermissions {
pub allow_print: bool,
pub allow_modify: bool,
pub allow_copy: bool,
pub allow_annotations: bool,
pub allow_fill_forms: bool,
pub allow_accessibility: bool,
pub allow_assemble: bool,
pub allow_print_high_quality: bool,
}
impl Default for PdfPermissions {
fn default() -> Self {
Self {
allow_print: true,
allow_modify: true,
allow_copy: true,
allow_annotations: true,
allow_fill_forms: true,
allow_accessibility: true,
allow_assemble: true,
allow_print_high_quality: true,
}
}
}
impl PdfPermissions {
pub fn to_p_value(&self) -> i32 {
let mut p: u32 = 0xFFFFF000;
p |= 0b1100_0000;
if self.allow_print {
p |= 1 << 2; }
if self.allow_modify {
p |= 1 << 3; }
if self.allow_copy {
p |= 1 << 4; }
if self.allow_annotations {
p |= 1 << 5; }
if self.allow_fill_forms {
p |= 1 << 8; }
if self.allow_accessibility {
p |= 1 << 9; }
if self.allow_assemble {
p |= 1 << 10; }
if self.allow_print_high_quality {
p |= 1 << 11; }
p as i32
}
}
#[derive(Debug, Clone)]
pub struct PdfSecurity {
pub owner_password: String,
pub user_password: String,
pub permissions: PdfPermissions,
pub key_length: u32,
pub algorithm: EncryptionAlgorithm,
}
impl PdfSecurity {
pub fn new(owner_password: &str, user_password: &str, permissions: PdfPermissions) -> Self {
Self {
owner_password: owner_password.to_string(),
user_password: user_password.to_string(),
permissions,
key_length: 128,
algorithm: EncryptionAlgorithm::Rc4128,
}
}
pub fn new_aes256(
owner_password: &str,
user_password: &str,
permissions: PdfPermissions,
) -> Self {
Self {
owner_password: owner_password.to_string(),
user_password: user_password.to_string(),
permissions,
key_length: 256,
algorithm: EncryptionAlgorithm::Aes256,
}
}
pub fn compute_encryption_dict(&self, file_id: &[u8]) -> EncryptionDict {
match self.algorithm {
EncryptionAlgorithm::Rc4128 => self.compute_rc4_encryption_dict(file_id),
EncryptionAlgorithm::Aes256 => self.compute_aes256_encryption_dict(),
}
}
fn compute_rc4_encryption_dict(&self, file_id: &[u8]) -> EncryptionDict {
let p_value = self.permissions.to_p_value();
let revision = if self.key_length > 40 { 3 } else { 2 };
let version = if self.key_length > 40 { 2 } else { 1 };
let key_len_bytes = (self.key_length / 8) as usize;
let o_value = self.compute_o_value(revision, key_len_bytes);
let encryption_key =
self.compute_encryption_key(&o_value, p_value, file_id, revision, key_len_bytes);
let u_value = self.compute_u_value(&encryption_key, file_id, revision);
EncryptionDict {
o_value,
u_value,
oe_value: None,
ue_value: None,
perms_value: None,
p_value,
encryption_key,
key_length: self.key_length,
revision: revision as u32,
version: version as u32,
algorithm: EncryptionAlgorithm::Rc4128,
}
}
fn compute_o_value(&self, revision: usize, key_len_bytes: usize) -> Vec<u8> {
let owner_padded = pad_password(&self.owner_password);
let mut hash = md5_hash(&owner_padded);
if revision >= 3 {
for _ in 0..50 {
hash = md5_hash(&hash[..key_len_bytes]);
}
}
let key = &hash[..key_len_bytes];
let user_padded = pad_password(&self.user_password);
let mut result = rc4_encrypt(key, &user_padded);
if revision >= 3 {
for i in 1..=19 {
let derived_key: Vec<u8> = key.iter().map(|&b| b ^ (i as u8)).collect();
result = rc4_encrypt(&derived_key, &result);
}
}
result
}
fn compute_encryption_key(
&self,
o_value: &[u8],
p_value: i32,
file_id: &[u8],
revision: usize,
key_len_bytes: usize,
) -> Vec<u8> {
let mut hasher = Md5::new();
let user_padded = pad_password(&self.user_password);
hasher.update(&user_padded);
hasher.update(o_value);
hasher.update((p_value as u32).to_le_bytes());
hasher.update(file_id);
let mut hash = hasher.finalize().to_vec();
if revision >= 3 {
for _ in 0..50 {
hash = md5_hash(&hash[..key_len_bytes]);
}
}
hash[..key_len_bytes].to_vec()
}
fn compute_u_value(&self, encryption_key: &[u8], file_id: &[u8], revision: usize) -> Vec<u8> {
if revision >= 3 {
let mut hasher = Md5::new();
hasher.update(PADDING);
hasher.update(file_id);
let hash = hasher.finalize().to_vec();
let mut result = rc4_encrypt(encryption_key, &hash);
for i in 1..=19 {
let derived_key: Vec<u8> = encryption_key.iter().map(|&b| b ^ (i as u8)).collect();
result = rc4_encrypt(&derived_key, &result);
}
result.resize(32, 0);
result
} else {
rc4_encrypt(encryption_key, &PADDING)
}
}
fn compute_aes256_encryption_dict(&self) -> EncryptionDict {
let p_value = self.permissions.to_p_value();
let user_validation_salt = derive_salt(&self.user_password, b"user_val");
let user_key_salt = derive_salt(&self.user_password, b"user_key");
let owner_validation_salt = derive_salt(&self.owner_password, b"owner_val");
let owner_key_salt = derive_salt(&self.owner_password, b"owner_key");
let u_hash = sha256_hash_parts(&[self.user_password.as_bytes(), &user_validation_salt]);
let mut u_value = u_hash.clone();
u_value.extend_from_slice(&user_validation_salt);
u_value.extend_from_slice(&user_key_salt);
let file_enc_key = derive_file_enc_key(&self.owner_password, &self.user_password);
let ue_key = sha256_hash_parts(&[self.user_password.as_bytes(), &user_key_salt]);
let ue_value = aes256_cbc_encrypt_no_iv(&file_enc_key, &ue_key);
let o_hash = sha256_hash_parts(&[
self.owner_password.as_bytes(),
&owner_validation_salt,
&u_value,
]);
let mut o_value = o_hash;
o_value.extend_from_slice(&owner_validation_salt);
o_value.extend_from_slice(&owner_key_salt);
let oe_key =
sha256_hash_parts(&[self.owner_password.as_bytes(), &owner_key_salt, &u_value]);
let oe_value = aes256_cbc_encrypt_no_iv(&file_enc_key, &oe_key);
let mut perms_plain = [0u8; 16];
let p_bytes = (p_value as u32).to_le_bytes();
perms_plain[0..4].copy_from_slice(&p_bytes);
perms_plain[4] = 0xFF;
perms_plain[5] = 0xFF;
perms_plain[6] = 0xFF;
perms_plain[7] = 0xFF;
perms_plain[8] = b'T'; perms_plain[9] = b'a';
perms_plain[10] = b'd';
perms_plain[11] = b'b';
perms_plain[12] = file_enc_key[0];
perms_plain[13] = file_enc_key[1];
perms_plain[14] = file_enc_key[2];
perms_plain[15] = file_enc_key[3];
let perms_value = aes256_ecb_encrypt_block(&perms_plain, &file_enc_key);
EncryptionDict {
o_value,
u_value,
oe_value: Some(oe_value),
ue_value: Some(ue_value),
perms_value: Some(perms_value.to_vec()),
p_value,
encryption_key: file_enc_key,
key_length: 256,
revision: 6,
version: 5,
algorithm: EncryptionAlgorithm::Aes256,
}
}
}
#[derive(Debug, Clone)]
pub struct EncryptionDict {
pub o_value: Vec<u8>,
pub u_value: Vec<u8>,
pub oe_value: Option<Vec<u8>>,
pub ue_value: Option<Vec<u8>>,
pub perms_value: Option<Vec<u8>>,
pub p_value: i32,
pub encryption_key: Vec<u8>,
pub key_length: u32,
pub revision: u32,
pub version: u32,
pub algorithm: EncryptionAlgorithm,
}
impl EncryptionDict {
pub fn encrypt_data(&self, data: &[u8], obj_num: u32, gen_num: u16) -> Vec<u8> {
match self.algorithm {
EncryptionAlgorithm::Rc4128 => {
let key = self.derive_object_key(obj_num, gen_num);
rc4_encrypt(&key, data)
}
EncryptionAlgorithm::Aes256 => {
encrypt_aes256_cbc(data, &self.encryption_key, obj_num)
}
}
}
fn derive_object_key(&self, obj_num: u32, gen_num: u16) -> Vec<u8> {
let mut hasher = Md5::new();
hasher.update(&self.encryption_key);
hasher.update(&obj_num.to_le_bytes()[..3]);
hasher.update(gen_num.to_le_bytes());
let hash = hasher.finalize();
let key_len = std::cmp::min(self.encryption_key.len() + 5, 16);
hash[..key_len].to_vec()
}
pub fn to_pdf_dict(&self, encrypt_obj_id: usize) -> String {
match self.algorithm {
EncryptionAlgorithm::Rc4128 => self.to_rc4_pdf_dict(encrypt_obj_id),
EncryptionAlgorithm::Aes256 => self.to_aes256_pdf_dict(encrypt_obj_id),
}
}
fn to_rc4_pdf_dict(&self, encrypt_obj_id: usize) -> String {
let mut dict = String::new();
dict.push_str(&format!("{} 0 obj\n", encrypt_obj_id));
dict.push_str("<<\n");
dict.push_str("/Filter /Standard\n");
dict.push_str(&format!("/V {}\n", self.version));
dict.push_str(&format!("/R {}\n", self.revision));
dict.push_str(&format!("/Length {}\n", self.key_length));
dict.push_str(&format!("/P {}\n", self.p_value));
dict.push_str(&format!("/O <{}>\n", hex_encode(&self.o_value)));
dict.push_str(&format!("/U <{}>\n", hex_encode(&self.u_value)));
dict.push_str(">>\n");
dict.push_str("endobj\n");
dict
}
fn to_aes256_pdf_dict(&self, encrypt_obj_id: usize) -> String {
let mut dict = String::new();
dict.push_str(&format!("{} 0 obj\n", encrypt_obj_id));
dict.push_str("<<\n");
dict.push_str("/Filter /Standard\n");
dict.push_str("/V 5\n");
dict.push_str("/R 6\n");
dict.push_str("/Length 256\n");
dict.push_str(&format!("/P {}\n", self.p_value));
dict.push_str(&format!("/O <{}>\n", hex_encode(&self.o_value)));
dict.push_str(&format!("/U <{}>\n", hex_encode(&self.u_value)));
if let Some(ref oe) = self.oe_value {
dict.push_str(&format!("/OE <{}>\n", hex_encode(oe)));
}
if let Some(ref ue) = self.ue_value {
dict.push_str(&format!("/UE <{}>\n", hex_encode(ue)));
}
if let Some(ref perms) = self.perms_value {
dict.push_str(&format!("/Perms <{}>\n", hex_encode(perms)));
}
dict.push_str("/CF <<\n");
dict.push_str(" /StdCF <<\n");
dict.push_str(" /AuthEvent /DocOpen\n");
dict.push_str(" /CFM /AESV3\n");
dict.push_str(" /Length 32\n");
dict.push_str(" >>\n");
dict.push_str(">>\n");
dict.push_str("/StmF /StdCF\n");
dict.push_str("/StrF /StdCF\n");
dict.push_str(">>\n");
dict.push_str("endobj\n");
dict
}
#[allow(dead_code)]
pub fn aes128_encrypt_block(&self, data: &[u8; 16]) -> [u8; 16] {
let cipher = Aes128::new_from_slice(&self.encryption_key[..16])
.expect("AES-128 key length is 16 bytes");
let mut block = aes::Block::try_from(data.as_slice()).expect("block is exactly 16 bytes");
cipher.encrypt_block(&mut block);
block.into()
}
#[allow(dead_code)]
pub fn encrypt_aes256(data: &[u8], key: &[u8]) -> Vec<u8> {
let iv = sha256_hash_parts(&[key, data])[..16].to_vec();
let mut iv_arr = [0u8; 16];
iv_arr.copy_from_slice(&iv);
type Aes256Cbc = cbc::Encryptor<Aes256>;
let cipher =
Aes256Cbc::new_from_slices(&key[..32], &iv_arr).expect("AES-256 key/IV lengths valid");
let ciphertext = aes256_cbc_encrypt_with_pkcs7(cipher, data);
let mut result = iv_arr.to_vec();
result.extend_from_slice(&ciphertext);
result
}
}
const PADDING: [u8; 32] = [
0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A,
];
fn pad_password(password: &str) -> Vec<u8> {
let pwd_bytes = password.as_bytes();
let mut padded = Vec::with_capacity(32);
if pwd_bytes.len() >= 32 {
padded.extend_from_slice(&pwd_bytes[..32]);
} else {
padded.extend_from_slice(pwd_bytes);
let remaining = 32 - pwd_bytes.len();
padded.extend_from_slice(&PADDING[..remaining]);
}
padded
}
fn md5_hash(data: &[u8]) -> Vec<u8> {
let mut hasher = Md5::new();
hasher.update(data);
hasher.finalize().to_vec()
}
fn sha256_hash_parts(parts: &[&[u8]]) -> Vec<u8> {
let mut hasher = Sha256::new();
for part in parts {
hasher.update(part);
}
hasher.finalize().to_vec()
}
fn rc4_encrypt(key: &[u8], data: &[u8]) -> Vec<u8> {
let mut s: Vec<u8> = (0..=255).map(|i| i as u8).collect();
let mut j: usize = 0;
for i in 0..256 {
j = (j + s[i] as usize + key[i % key.len()] as usize) % 256;
s.swap(i, j);
}
let mut output = Vec::with_capacity(data.len());
let mut i: usize = 0;
j = 0;
for &byte in data {
i = (i + 1) % 256;
j = (j + s[i] as usize) % 256;
s.swap(i, j);
let k = s[(s[i] as usize + s[j] as usize) % 256];
output.push(byte ^ k);
}
output
}
fn encrypt_aes256_cbc(data: &[u8], key: &[u8], obj_num: u32) -> Vec<u8> {
let mut iv_src = [0u8; 36];
iv_src[..32].copy_from_slice(&key[..32]);
iv_src[32..36].copy_from_slice(&obj_num.to_le_bytes());
let iv_hash = sha256_hash_parts(&[&iv_src]);
let mut iv = [0u8; 16];
iv.copy_from_slice(&iv_hash[..16]);
type Aes256Cbc = cbc::Encryptor<Aes256>;
let cipher = Aes256Cbc::new_from_slices(&key[..32], &iv).expect("AES-256 key/IV lengths valid");
let ciphertext = aes256_cbc_encrypt_with_pkcs7(cipher, data);
let mut result = iv.to_vec();
result.extend_from_slice(&ciphertext);
result
}
fn aes256_cbc_encrypt_no_iv(data: &[u8], key: &[u8]) -> Vec<u8> {
let iv = [0u8; 16];
type Aes256Cbc = cbc::Encryptor<Aes256>;
let cipher = Aes256Cbc::new_from_slices(&key[..32], &iv).expect("AES-256 key/IV lengths valid");
aes256_cbc_encrypt_with_pkcs7(cipher, data)
}
fn aes256_ecb_encrypt_block(data: &[u8; 16], key: &[u8]) -> [u8; 16] {
let cipher = Aes256::new_from_slice(&key[..32]).expect("AES-256 key length is 32 bytes");
let mut block = aes::Block::try_from(data.as_slice()).expect("block is exactly 16 bytes");
cipher.encrypt_block(&mut block);
block.into()
}
fn pkcs7_pad(data: &[u8], block_size: usize) -> Vec<u8> {
let pad_len = block_size - (data.len() % block_size);
let mut padded = data.to_vec();
for _ in 0..pad_len {
padded.push(pad_len as u8);
}
padded
}
fn aes256_cbc_encrypt_with_pkcs7(mut cipher: cbc::Encryptor<Aes256>, data: &[u8]) -> Vec<u8> {
use cbc::cipher::BlockModeEncrypt;
let padded = pkcs7_pad(data, 16);
let mut out = padded;
let block_count = out.len() / 16;
for i in 0..block_count {
let start = i * 16;
let end = start + 16;
let mut block = aes::Block::try_from(&out[start..end]).expect("block is exactly 16 bytes");
cipher.encrypt_block(&mut block);
out[start..end].copy_from_slice(&block);
}
out
}
fn derive_salt(password: &str, tag: &[u8]) -> [u8; 8] {
let hash = sha256_hash_parts(&[password.as_bytes(), tag]);
let mut salt = [0u8; 8];
salt.copy_from_slice(&hash[..8]);
salt
}
fn derive_file_enc_key(owner_pass: &str, user_pass: &str) -> Vec<u8> {
sha256_hash_parts(&[owner_pass.as_bytes(), user_pass.as_bytes(), b"file_enc_key"])
}
fn hex_encode(data: &[u8]) -> String {
data.iter().map(|b| format!("{:02X}", b)).collect()
}
pub fn generate_file_id(seed: &str) -> Vec<u8> {
md5_hash(seed.as_bytes())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pad_password_empty() {
let padded = pad_password("");
assert_eq!(padded.len(), 32);
assert_eq!(padded, PADDING.to_vec());
}
#[test]
fn test_pad_password_short() {
let padded = pad_password("test");
assert_eq!(padded.len(), 32);
assert_eq!(&padded[..4], b"test");
assert_eq!(&padded[4..], &PADDING[..28]);
}
#[test]
fn test_pad_password_long() {
let long_pwd = "a".repeat(40);
let padded = pad_password(&long_pwd);
assert_eq!(padded.len(), 32);
assert_eq!(padded, vec![b'a'; 32]);
}
#[test]
fn test_permissions_default_all_allowed() {
let perms = PdfPermissions::default();
let p = perms.to_p_value();
assert!(p & (1 << 2) != 0); assert!(p & (1 << 3) != 0); assert!(p & (1 << 4) != 0); assert!(p & (1 << 5) != 0); }
#[test]
fn test_permissions_restricted() {
let perms = PdfPermissions {
allow_print: false,
allow_copy: false,
allow_modify: false,
allow_annotations: false,
..Default::default()
};
let p = perms.to_p_value();
assert!(p & (1 << 2) == 0); assert!(p & (1 << 3) == 0); assert!(p & (1 << 4) == 0); assert!(p & (1 << 5) == 0); }
#[test]
fn test_rc4_encrypt_decrypt_roundtrip() {
let key = b"testkey123456789";
let plaintext = b"Hello, World! This is a PDF encryption test.";
let encrypted = rc4_encrypt(key, plaintext);
let decrypted = rc4_encrypt(key, &encrypted);
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_rc4_encryption_dict_computation() {
let security = PdfSecurity::new("owner", "user", PdfPermissions::default());
let file_id = generate_file_id("test-document");
let dict = security.compute_encryption_dict(&file_id);
assert_eq!(dict.o_value.len(), 32);
assert_eq!(dict.u_value.len(), 32);
assert_eq!(dict.key_length, 128);
assert_eq!(dict.revision, 3);
assert_eq!(dict.version, 2);
assert_eq!(dict.algorithm, EncryptionAlgorithm::Rc4128);
}
#[test]
fn test_rc4_object_encryption_roundtrip() {
let security = PdfSecurity::new("owner", "user", PdfPermissions::default());
let file_id = generate_file_id("test-doc");
let dict = security.compute_encryption_dict(&file_id);
let plaintext = b"This is a test stream content for PDF object.";
let encrypted = dict.encrypt_data(plaintext, 5, 0);
let decrypted = dict.encrypt_data(&encrypted, 5, 0);
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_rc4_encryption_dict_pdf_output() {
let security = PdfSecurity::new("owner", "", PdfPermissions::default());
let file_id = generate_file_id("test");
let dict = security.compute_encryption_dict(&file_id);
let pdf_str = dict.to_pdf_dict(10);
assert!(pdf_str.contains("/Filter /Standard"));
assert!(pdf_str.contains("/V 2"));
assert!(pdf_str.contains("/R 3"));
assert!(pdf_str.contains("/Length 128"));
assert!(pdf_str.contains("/O <"));
assert!(pdf_str.contains("/U <"));
}
#[test]
fn test_aes256_encryption_dict_computation() {
let security =
PdfSecurity::new_aes256("owner_pass", "user_pass", PdfPermissions::default());
let dict = security.compute_encryption_dict(&[]);
assert_eq!(dict.o_value.len(), 48);
assert_eq!(dict.u_value.len(), 48);
assert_eq!(dict.key_length, 256);
assert_eq!(dict.revision, 6);
assert_eq!(dict.version, 5);
assert_eq!(dict.algorithm, EncryptionAlgorithm::Aes256);
assert!(dict.oe_value.is_some());
assert!(dict.ue_value.is_some());
assert!(dict.perms_value.is_some());
assert_eq!(
dict.perms_value
.as_ref()
.expect("test: should succeed")
.len(),
16
);
}
#[test]
fn test_aes256_encryption_dict_pdf_output() {
let security =
PdfSecurity::new_aes256("owner_pass", "user_pass", PdfPermissions::default());
let dict = security.compute_encryption_dict(&[]);
let pdf_str = dict.to_pdf_dict(10);
assert!(pdf_str.contains("/Filter /Standard"));
assert!(pdf_str.contains("/V 5"));
assert!(pdf_str.contains("/R 6"));
assert!(pdf_str.contains("/Length 256"));
assert!(pdf_str.contains("/O <"));
assert!(pdf_str.contains("/U <"));
assert!(pdf_str.contains("/OE <"));
assert!(pdf_str.contains("/UE <"));
assert!(pdf_str.contains("/Perms <"));
assert!(pdf_str.contains("/CFM /AESV3"));
assert!(pdf_str.contains("/StmF /StdCF"));
assert!(pdf_str.contains("/StrF /StdCF"));
}
#[test]
fn test_aes256_stream_encryption() {
let security =
PdfSecurity::new_aes256("owner_pass", "user_pass", PdfPermissions::default());
let dict = security.compute_encryption_dict(&[]);
let plaintext = b"Sensitive PDF stream content.";
let encrypted = dict.encrypt_data(plaintext, 10, 0);
assert!(encrypted.len() > plaintext.len());
assert_ne!(&encrypted[16..16 + plaintext.len()], plaintext.as_slice());
}
#[test]
fn test_aes256_deterministic() {
let security =
PdfSecurity::new_aes256("owner_pass", "user_pass", PdfPermissions::default());
let dict = security.compute_encryption_dict(&[]);
let plaintext = b"Test data for AES-256.";
let enc1 = dict.encrypt_data(plaintext, 5, 0);
let enc2 = dict.encrypt_data(plaintext, 5, 0);
assert_eq!(enc1, enc2);
}
#[test]
fn test_hex_encode() {
assert_eq!(hex_encode(&[0xFF, 0x00, 0xAB]), "FF00AB");
assert_eq!(hex_encode(&[]), "");
}
#[test]
fn test_file_id_generation() {
let id1 = generate_file_id("doc1");
let id2 = generate_file_id("doc2");
assert_eq!(id1.len(), 16);
assert_ne!(id1, id2);
}
#[test]
fn test_encryption_algorithm_default() {
let algo = EncryptionAlgorithm::default();
assert_eq!(algo, EncryptionAlgorithm::Rc4128);
}
#[test]
fn test_pkcs7_pad() {
let data = b"Hello, World!";
let padded = pkcs7_pad(data, 16);
assert_eq!(padded.len(), 16);
assert_eq!(&padded[13..], &[3u8, 3, 3]);
}
}
#[cfg(test)]
mod tests_extended {
use super::*;
#[test]
fn test_permissions_all_denied() {
let perms = PdfPermissions {
allow_print: false,
allow_modify: false,
allow_copy: false,
allow_annotations: false,
allow_fill_forms: false,
allow_accessibility: false,
allow_assemble: false,
allow_print_high_quality: false,
};
let p = perms.to_p_value();
assert_eq!(p & (1 << 2), 0); assert_eq!(p & (1 << 3), 0); assert_eq!(p & (1 << 4), 0); assert_eq!(p & (1 << 5), 0); assert_eq!(p & (1 << 8), 0); assert_eq!(p & (1 << 9), 0); assert_eq!(p & (1 << 10), 0); assert_eq!(p & (1 << 11), 0); }
#[test]
fn test_permissions_only_print_allowed() {
let perms = PdfPermissions {
allow_print: true,
allow_modify: false,
allow_copy: false,
allow_annotations: false,
allow_fill_forms: false,
allow_accessibility: false,
allow_assemble: false,
allow_print_high_quality: false,
};
let p = perms.to_p_value();
assert_ne!(p & (1 << 2), 0); assert_eq!(p & (1 << 3), 0); assert_eq!(p & (1 << 4), 0); }
#[test]
fn test_pdf_security_new_stores_passwords() {
let perms = PdfPermissions::default();
let sec = PdfSecurity::new("owner_pw", "user_pw", perms);
assert_eq!(sec.owner_password, "owner_pw");
assert_eq!(sec.user_password, "user_pw");
assert_eq!(sec.key_length, 128);
assert_eq!(sec.algorithm, EncryptionAlgorithm::Rc4128);
}
#[test]
fn test_pdf_security_aes256_stores_correct_length() {
let perms = PdfPermissions::default();
let sec = PdfSecurity::new_aes256("owner", "user", perms);
assert_eq!(sec.key_length, 256);
assert_eq!(sec.algorithm, EncryptionAlgorithm::Aes256);
}
#[test]
fn test_rc4_different_objects_produce_different_ciphertext() {
let sec = PdfSecurity::new("owner", "user", PdfPermissions::default());
let file_id = generate_file_id("doc");
let dict = sec.compute_encryption_dict(&file_id);
let plaintext = b"same plaintext for both objects";
let enc_obj1 = dict.encrypt_data(plaintext, 1, 0);
let enc_obj2 = dict.encrypt_data(plaintext, 2, 0);
assert_ne!(enc_obj1, enc_obj2);
}
#[test]
fn test_aes256_different_objects_produce_different_ciphertext() {
let sec = PdfSecurity::new_aes256("owner", "user", PdfPermissions::default());
let dict = sec.compute_encryption_dict(&[]);
let plaintext = b"same plaintext";
let enc_obj1 = dict.encrypt_data(plaintext, 1, 0);
let enc_obj2 = dict.encrypt_data(plaintext, 2, 0);
assert_ne!(enc_obj1, enc_obj2);
}
#[test]
fn test_rc4_empty_plaintext() {
let sec = PdfSecurity::new("owner", "user", PdfPermissions::default());
let file_id = generate_file_id("doc");
let dict = sec.compute_encryption_dict(&file_id);
let encrypted = dict.encrypt_data(b"", 1, 0);
assert!(encrypted.is_empty());
}
#[test]
fn test_generate_file_id_is_16_bytes() {
let id = generate_file_id("test");
assert_eq!(id.len(), 16);
}
#[test]
fn test_generate_file_id_different_seeds_differ() {
let a = generate_file_id("seed_a");
let b = generate_file_id("seed_b");
assert_ne!(a, b);
}
#[test]
fn test_hex_encode_all_bytes() {
let data: Vec<u8> = (0..=15).collect();
let hex = hex_encode(&data);
assert_eq!(hex, "000102030405060708090A0B0C0D0E0F");
}
#[test]
fn test_pkcs7_pad_exact_block() {
let data = b"1234567890123456";
let padded = pkcs7_pad(data, 16);
assert_eq!(padded.len(), 32);
assert_eq!(&padded[16..], &[16u8; 16]);
}
#[test]
fn test_encryption_dict_clone() {
let sec = PdfSecurity::new("o", "u", PdfPermissions::default());
let file_id = generate_file_id("clone");
let dict = sec.compute_encryption_dict(&file_id);
let dict2 = dict.clone();
assert_eq!(dict.key_length, dict2.key_length);
assert_eq!(dict.algorithm, dict2.algorithm);
}
#[test]
fn test_encryption_algorithm_debug() {
let rc4 = EncryptionAlgorithm::Rc4128;
let aes = EncryptionAlgorithm::Aes256;
assert!(format!("{:?}", rc4).contains("Rc4"));
assert!(format!("{:?}", aes).contains("Aes"));
}
}