use pdfrum_object::{Dict, Name, ObjRef, Resolve, names};
use zeroize::Zeroize;
use crate::key::SmallKey;
use crate::object::{self, CryptClass, Iv};
use crate::permissions::Permissions;
use crate::standard::{self, Cipher, EncryptParams, PasswordEncoding, parse_encrypt_dict};
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("the supplied password is not the user or owner password")]
WrongPassword,
#[error("the operating system's random generator is unavailable")]
NoEntropy,
#[error("/Filter {0:?} is not the standard security handler")]
UnsupportedHandler(Box<[u8]>),
#[error("/StmF and /StrF name different crypt filters")]
MismatchedCryptFilters,
#[error("crypt filter {0:?} is not present in /CF")]
MissingCryptFilter(Box<[u8]>),
#[error("/Encrypt is malformed: {0}")]
MalformedEncryptDict(&'static str),
#[error("key length {len} bytes is invalid for {cipher}")]
CipherKeyLength {
cipher: &'static str,
len: usize,
},
}
#[derive(Debug, Clone)]
pub enum SecurityHandler {
Rc4V2 {
key: SmallKey,
revision: u8,
permissions: u32,
owner_unlocked: bool,
encrypt_metadata: bool,
encoding: PasswordEncoding,
embedded_cipher: Option<Cipher>,
strings_identity: bool,
},
AesV4 {
key: SmallKey,
revision: u8,
permissions: u32,
owner_unlocked: bool,
encrypt_metadata: bool,
encoding: PasswordEncoding,
embedded_cipher: Option<Cipher>,
strings_identity: bool,
},
AesV5 {
key: Box<[u8; 32]>,
revision: u8,
permissions: u32,
owner_unlocked: bool,
encrypt_metadata: bool,
encoding: PasswordEncoding,
embedded_cipher: Option<Cipher>,
strings_identity: bool,
},
Identity,
}
impl Drop for SecurityHandler {
fn drop(&mut self) {
if let Self::AesV5 { key, .. } = self {
key.zeroize();
}
}
}
impl SecurityHandler {
pub fn from_encrypt_dict(
dict: &Dict,
file_id: &[u8],
password: &[u8],
r: &impl Resolve,
) -> Result<Self, Error> {
let params = parse_encrypt_dict(dict, r)?;
if params.cipher == Cipher::None {
return Ok(Self::Identity);
}
if !password.is_empty()
&& let Some(unlocked) = standard::try_password(¶ms, password, true, file_id)
{
return Ok(Self::assemble(¶ms, unlocked, true));
}
standard::try_password(¶ms, password, false, file_id)
.map(|unlocked| Self::assemble(¶ms, unlocked, false))
.ok_or(Error::WrongPassword)
}
fn assemble(
params: &EncryptParams,
unlocked: standard::Unlocked,
owner_unlocked: bool,
) -> Self {
let standard::Unlocked { key, encoding } = unlocked;
let revision = u8::try_from(params.revision).unwrap_or(u8::MAX);
let permissions = params.permissions;
let encrypt_metadata = params.encrypt_metadata;
let strings_identity = params.string_cipher == Cipher::None;
match params.cipher {
Cipher::None => Self::Identity,
Cipher::Rc4 => Self::Rc4V2 {
key,
revision,
permissions,
owner_unlocked,
encrypt_metadata,
encoding,
embedded_cipher: params.embedded_cipher,
strings_identity,
},
Cipher::Aes if key.len() == SmallKey::MAX_LEN => {
let mut full = [0u8; 32];
if let Some(head) = full.get_mut(..key.len()) {
head.copy_from_slice(key.bytes());
}
Self::AesV5 {
key: Box::new(full),
revision,
permissions,
owner_unlocked,
encrypt_metadata,
encoding,
embedded_cipher: params.embedded_cipher,
strings_identity,
}
}
Cipher::Aes => Self::AesV4 {
key,
revision,
permissions,
owner_unlocked,
encrypt_metadata,
encoding,
embedded_cipher: params.embedded_cipher,
strings_identity,
},
}
}
#[must_use]
pub const fn strings_identity(&self) -> bool {
match self {
Self::Identity => false,
Self::Rc4V2 {
strings_identity, ..
}
| Self::AesV4 {
strings_identity, ..
}
| Self::AesV5 {
strings_identity, ..
} => *strings_identity,
}
}
#[must_use]
pub fn decrypt(&self, obj: ObjRef, class: CryptClass, data: &[u8]) -> Vec<u8> {
if class == CryptClass::String && self.strings_identity() {
return data.to_vec();
}
if let (CryptClass::Embedded, Some(cipher)) = (class, self.embedded_cipher()) {
return self.decrypt_with(obj, cipher, data);
}
match self {
Self::Identity => data.to_vec(),
Self::Rc4V2 { key, .. } => object::decrypt_rc4(key, obj, data),
Self::AesV4 { key, .. } => object::decrypt_aes_v4(key, obj, data),
Self::AesV5 { key, .. } => object::decrypt_aes_v5(key, data),
}
}
#[must_use]
pub fn embedded_cipher(&self) -> Option<Cipher> {
match self {
Self::Identity => None,
Self::Rc4V2 {
embedded_cipher, ..
}
| Self::AesV4 {
embedded_cipher, ..
}
| Self::AesV5 {
embedded_cipher, ..
} => *embedded_cipher,
}
}
fn decrypt_with(&self, obj: ObjRef, cipher: Cipher, data: &[u8]) -> Vec<u8> {
let Some(key) = self.file_key() else {
return data.to_vec();
};
match cipher {
Cipher::None => data.to_vec(),
Cipher::Rc4 => object::decrypt_rc4(&key, obj, data),
Cipher::Aes => match <[u8; 32]>::try_from(key.bytes()) {
Ok(full) => object::decrypt_aes_v5(&full, data),
Err(_) => object::decrypt_aes_v4(&key, obj, data),
},
}
}
#[must_use]
pub fn encrypt(&self, obj: ObjRef, class: CryptClass, iv: Iv, data: &[u8]) -> Vec<u8> {
if class == CryptClass::String && self.strings_identity() {
return data.to_vec();
}
if data.is_empty() {
return Vec::new();
}
if let (CryptClass::Embedded, Some(cipher)) = (class, self.embedded_cipher()) {
return self.encrypt_with(obj, cipher, iv, data);
}
match self {
Self::Identity => data.to_vec(),
Self::Rc4V2 { key, .. } => object::encrypt_rc4(key, obj, data),
Self::AesV4 { key, .. } => object::encrypt_aes_v4(key, obj, iv.bytes(), data),
Self::AesV5 { key, .. } => object::encrypt_aes_v5(key, iv.bytes(), data),
}
}
fn encrypt_with(&self, obj: ObjRef, cipher: Cipher, iv: Iv, data: &[u8]) -> Vec<u8> {
let Some(key) = self.file_key() else {
return data.to_vec();
};
match cipher {
Cipher::None => data.to_vec(),
Cipher::Rc4 => object::encrypt_rc4(&key, obj, data),
Cipher::Aes => match <[u8; 32]>::try_from(key.bytes()) {
Ok(full) => object::encrypt_aes_v5(&full, iv.bytes(), data),
Err(_) => object::encrypt_aes_v4(&key, obj, iv.bytes(), data),
},
}
}
fn file_key(&self) -> Option<SmallKey> {
match self {
Self::Identity => None,
Self::Rc4V2 { key, .. } | Self::AesV4 { key, .. } => Some(key.clone()),
Self::AesV5 { key, .. } => Some(SmallKey::from_full(**key)),
}
}
#[must_use]
pub fn permissions(&self) -> Permissions {
Permissions::from_bits(self.permission_word(false))
}
#[must_use]
pub fn owner_permissions(&self) -> Permissions {
Permissions::from_bits(self.permission_word(true))
}
pub(crate) fn permission_word(&self, owner: bool) -> u32 {
let (permissions, owner_unlocked) = match self {
Self::Identity => return 0xFFFF_FFFF,
Self::Rc4V2 {
permissions,
owner_unlocked,
..
}
| Self::AesV4 {
permissions,
owner_unlocked,
..
}
| Self::AesV5 {
permissions,
owner_unlocked,
..
} => (*permissions, *owner_unlocked),
};
let base = if owner_unlocked && owner {
0xFFFF_FFFF
} else {
permissions
};
(base & 0xFFFF_FFFC) | 0xFFFF_F0C0
}
#[must_use]
pub fn encrypt_metadata(&self) -> bool {
match self {
Self::Identity => true,
Self::Rc4V2 {
encrypt_metadata, ..
}
| Self::AesV4 {
encrypt_metadata, ..
}
| Self::AesV5 {
encrypt_metadata, ..
} => *encrypt_metadata,
}
}
#[must_use]
pub fn revision(&self) -> u8 {
match self {
Self::Identity => 0,
Self::Rc4V2 { revision, .. }
| Self::AesV4 { revision, .. }
| Self::AesV5 { revision, .. } => *revision,
}
}
#[must_use]
pub fn owner_unlocked(&self) -> bool {
match self {
Self::Identity => false,
Self::Rc4V2 { owner_unlocked, .. }
| Self::AesV4 { owner_unlocked, .. }
| Self::AesV5 { owner_unlocked, .. } => *owner_unlocked,
}
}
#[must_use]
pub fn password_encoding(&self) -> PasswordEncoding {
match self {
Self::Identity => PasswordEncoding::AsGiven,
Self::Rc4V2 { encoding, .. }
| Self::AesV4 { encoding, .. }
| Self::AesV5 { encoding, .. } => *encoding,
}
}
}
#[must_use]
pub fn is_signature_dict(dict: &Dict) -> bool {
let key = if dict.contains_key(names::TYPE) {
names::TYPE
} else {
names::FT
};
signature_valued(dict, key)
}
fn signature_valued(dict: &Dict, key: &Name) -> bool {
dict.raw(key).is_some_and(|value| {
value.as_name().is_some_and(|n| n == names::SIG)
|| value
.as_string()
.is_some_and(|s| &*s.bytes == names::SIG.as_bytes())
})
}