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,
}Expand description
A document’s decryption state: which cipher, which key, and what the password unlocked.
A closed enum rather than a trait, because the set of standard security
handlers is closed: adding a variant must break every match on it.
Rc4V2 covers /V 1 to /V 4 without an AES crypt filter; AesV4 is
AESV2, whose per-object key is an MD5 over the object number and the four
bytes sAlT; AesV5 is AESV3, whose 32-byte key is used verbatim with no
per-object derivation; Identity is both an unencrypted document and one
naming /Identity as its crypt filter.
Variants§
Rc4V2
RC4, with a 5- to 16-byte file key.
Fields
encoding: PasswordEncodingWhich password spelling worked.
AesV4
AESV2: a 16- or 24-byte file key with per-object sAlT derivation.
Fields
encoding: PasswordEncodingWhich password spelling worked.
AesV5
AESV3 (/V 5, revision 5 or 6): the 32-byte key is used as-is.
Fields
encoding: PasswordEncodingWhich password spelling worked.
Identity
No encryption, or /StrF /Identity.
Implementations§
Source§impl SecurityHandler
impl SecurityHandler
Sourcepub fn from_encrypt_dict(
dict: &Dict,
file_id: &[u8],
password: &[u8],
r: &impl Resolve,
) -> Result<Self, Error>
pub fn from_encrypt_dict( dict: &Dict, file_id: &[u8], password: &[u8], r: &impl Resolve, ) -> Result<Self, Error>
Build a handler from the trailer’s /Encrypt dictionary.
file_id is the first element of the trailer’s /ID array as raw
bytes; pass &[] when /ID is absent, which contributes nothing to
the key rather than an empty marker. password is raw bytes, uncapped
— the specification’s 127-byte limit is not enforced. A non-empty
password is tried as the owner password first and only then as the
user password; an empty one is only ever a user password.
use pdfrum_crypt::{Error, SecurityHandler};
use pdfrum_object::{Dict, NoResolve, Object, PdfString, names};
// A public-key handler is not the standard one.
let dict = Dict::from_pairs([(
names::FILTER.clone(),
Object::Name(pdfrum_object::Name::from("Adobe.PubSec")),
)]);
assert!(matches!(
SecurityHandler::from_encrypt_dict(&dict, &[], b"", &NoResolve),
Err(Error::UnsupportedHandler(_))
));§Errors
Error::WrongPassword when neither role accepts the password, and
the parse errors of parse_encrypt_dict when the dictionary itself
cannot be used.
Sourcepub const fn strings_identity(&self) -> bool
pub const fn strings_identity(&self) -> bool
[oracle-bug] Whether the string class resolved to /Identity while
the stream class did not, so strings in this document are plaintext.
Always false for Self::Identity, which has nothing to contrast
against — an unencrypted document’s strings are plaintext anyway.
Sourcepub fn decrypt(&self, obj: ObjRef, class: CryptClass, data: &[u8]) -> Vec<u8> ⓘ
pub fn decrypt(&self, obj: ObjRef, class: CryptClass, data: &[u8]) -> Vec<u8> ⓘ
Decrypt one string or stream payload belonging to indirect object
obj.
Infallible by design: PDFium never fails a decrypt, it produces a best-effort result. An AES payload shorter than seventeen bytes, a trailing partial block, and a final block whose padding byte is out of range all yield less output than input rather than an error.
obj must be the enclosing indirect object, not a nested one: a
direct string inside an indirect dictionary is keyed by the
dictionary’s number and generation.
use pdfrum_crypt::{CryptClass, SecurityHandler};
use pdfrum_object::ObjRef;
// Fewer than seventeen bytes of AES ciphertext is all initialisation
// vector and no payload.
let handler = SecurityHandler::AesV5 {
key: Box::new([0; 32]),
revision: 6,
permissions: 0xFFFF_FFFC,
owner_unlocked: false,
encrypt_metadata: true,
encoding: pdfrum_crypt::PasswordEncoding::AsGiven,
embedded_cipher: None, // no /EFF: the stream cipher serves
strings_identity: false,
};
assert!(handler.decrypt(ObjRef::new(4, 0), CryptClass::String, &[0; 16]).is_empty());Sourcepub fn embedded_cipher(&self) -> Option<Cipher>
pub fn embedded_cipher(&self) -> Option<Cipher>
The /EFF cipher override, or None when the embedded class uses the
stream cipher — which ISO 32000-1 §7.6.5 table 20 makes the default.
Sourcepub fn encrypt(
&self,
obj: ObjRef,
class: CryptClass,
iv: Iv,
data: &[u8],
) -> Vec<u8> ⓘ
pub fn encrypt( &self, obj: ObjRef, class: CryptClass, iv: Iv, data: &[u8], ) -> Vec<u8> ⓘ
Encipher one string or stream payload belonging to indirect object
obj, the inverse of SecurityHandler::decrypt.
iv must be fresh per payload for the cipher to be sound; the RC4
handler and Self::Identity ignore it. Infallible, like its inverse.
RC4 preserves length exactly. AES grows a payload of n bytes to
32 + 16 * (n / 16): sixteen for the vector, and a PKCS#7 pad that is
always present, so an already block-aligned payload gains a whole
block. An empty payload is the exception and stays empty, so a save
does not grow every empty string in a document by 32 bytes.
use pdfrum_crypt::{CryptClass, Iv, SecurityHandler};
use pdfrum_object::ObjRef;
let handler = SecurityHandler::AesV5 {
key: Box::new([0; 32]),
revision: 6,
permissions: 0xFFFF_FFFC,
owner_unlocked: false,
encrypt_metadata: true,
encoding: pdfrum_crypt::PasswordEncoding::AsGiven,
embedded_cipher: None, // no /EFF: the stream cipher serves
strings_identity: false,
};
let obj = ObjRef::new(4, 0);
let sealed = handler.encrypt(obj, CryptClass::String, Iv([7; 16]), b"secret");
// Sixteen of vector, one block of ciphertext.
assert_eq!(sealed.len(), 32);
assert_eq!(handler.decrypt(obj, CryptClass::String, &sealed), b"secret");Sourcepub fn permissions(&self) -> Permissions
pub fn permissions(&self) -> Permissions
What the document permits, for the password that opened it.
A document opened with the owner password reports what /P
allows, the same as a user reading of it; the owner’s own unrestricted
view is SecurityHandler::owner_permissions. An unencrypted document
has no restrictions at all.
The ISO table-22 decode lives in Permissions, next to the /P
word, so no caller has to spell bits & 0x100.
assert_eq!(SecurityHandler::Identity.permissions(), Permissions::ALL);Sourcepub fn owner_permissions(&self) -> Permissions
pub fn owner_permissions(&self) -> Permissions
What the document permits under the owner’s view.
A document the owner password opened reports every permission
granted here, whatever /P says, because the owner may lift every
restriction. For a document the user password opened — and for an
unencrypted one — this is the same answer as
SecurityHandler::permissions.
assert_eq!(SecurityHandler::Identity.owner_permissions(), Permissions::ALL);Sourcepub fn encrypt_metadata(&self) -> bool
pub fn encrypt_metadata(&self) -> bool
Whether the document’s metadata stream is encrypted (/EncryptMetadata,
default true).
The parser consults this to decide whether to skip decrypting the
object /Root/Metadata points at.
Sourcepub fn owner_unlocked(&self) -> bool
pub fn owner_unlocked(&self) -> bool
Whether the owner password opened this document.
Sourcepub fn password_encoding(&self) -> PasswordEncoding
pub fn password_encoding(&self) -> PasswordEncoding
Which spelling of the password unlocked the document.
Trait Implementations§
Source§impl Clone for SecurityHandler
impl Clone for SecurityHandler
Source§fn clone(&self) -> SecurityHandler
fn clone(&self) -> SecurityHandler
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more