use super::{EncryptionInfo, PdfEncryption, PdfEncryptionAlgorithm};
use crate::crypto::CryptoError;
pub fn encrypt_pdf(pdf_bytes: &[u8], encryption: &PdfEncryption) -> Result<Vec<u8>, CryptoError> {
let mut doc = lopdf::Document::load_mem(pdf_bytes)
.map_err(|e| CryptoError::InvalidEncryptedPdf(format!("failed to parse PDF: {e}")))?;
let fek = generate_file_encryption_key(encryption);
let version = build_encryption_version(&doc, encryption, &fek);
let state = lopdf::EncryptionState::try_from(version)
.map_err(|e| CryptoError::Aes(format!("encryption state derivation failed: {e}")))?;
doc.encrypt(&state)
.map_err(|e| CryptoError::Aes(format!("encrypt failed: {e}")))?;
let mut buf = Vec::new();
doc.save_to(&mut buf)
.map_err(|e| CryptoError::Io(std::io::Error::other(e.to_string())))?;
Ok(buf)
}
pub fn decrypt_pdf(encrypted_bytes: &[u8], password: &str) -> Result<Vec<u8>, CryptoError> {
let mut doc = lopdf::Document::load_mem(encrypted_bytes)
.map_err(|e| CryptoError::InvalidEncryptedPdf(format!("failed to parse PDF: {e}")))?;
doc.decrypt(password).map_err(|e| map_decrypt_error(&e))?;
let mut buf = Vec::new();
doc.save_to(&mut buf)
.map_err(|e| CryptoError::Io(std::io::Error::other(e.to_string())))?;
Ok(buf)
}
pub fn get_encryption_info(encrypted_bytes: &[u8]) -> Result<Option<EncryptionInfo>, CryptoError> {
let doc = lopdf::Document::load_mem(encrypted_bytes)
.map_err(|e| CryptoError::InvalidEncryptedPdf(format!("failed to parse PDF: {e}")))?;
if !doc.is_encrypted() {
return Ok(None);
}
let enc_dict = doc
.get_encrypted()
.map_err(|e| CryptoError::InvalidEncryptedPdf(format!("no /Encrypt dict: {e}")))?;
let version = enc_dict
.get(b"V")
.ok()
.and_then(|o| o.as_i64().ok())
.unwrap_or(0);
let revision = enc_dict
.get(b"R")
.ok()
.and_then(|o| o.as_i64().ok())
.unwrap_or(0);
let length = enc_dict
.get(b"Length")
.ok()
.and_then(|o| o.as_i64().ok())
.and_then(|l| u16::try_from(l).ok());
let algorithm = match version {
4 => PdfEncryptionAlgorithm::Aes128,
5 => PdfEncryptionAlgorithm::Aes256,
_ => return Ok(None),
};
Ok(Some(EncryptionInfo {
algorithm,
version,
revision,
key_length_bits: length,
}))
}
fn generate_file_encryption_key(enc: &PdfEncryption) -> [u8; 32] {
use rand::RngCore;
match enc.algorithm {
PdfEncryptionAlgorithm::Aes256 => {
let mut key = [0u8; 32];
rand::thread_rng().fill_bytes(&mut key);
key
}
PdfEncryptionAlgorithm::Aes128 => [0u8; 32], }
}
fn map_decrypt_error(e: &lopdf::Error) -> CryptoError {
match e {
lopdf::Error::InvalidPassword | lopdf::Error::Decryption(_) => {
CryptoError::InvalidPassword(e.to_string())
}
_ => CryptoError::InvalidEncryptedPdf(format!("decryption failed: {e}")),
}
}
fn build_encryption_version<'a>(
doc: &'a lopdf::Document,
enc: &'a PdfEncryption,
file_encryption_key: &'a [u8; 32],
) -> lopdf::EncryptionVersion<'a> {
let permissions = enc.permissions.to_lopdf();
let stdcf = b"StdCF".to_vec();
match enc.algorithm {
PdfEncryptionAlgorithm::Aes128 => {
let crypt_filter: std::sync::Arc<dyn lopdf::encryption::crypt_filters::CryptFilter> =
std::sync::Arc::new(lopdf::encryption::crypt_filters::Aes128CryptFilter);
lopdf::EncryptionVersion::V4 {
document: doc,
encrypt_metadata: true,
crypt_filters: std::collections::BTreeMap::from([(stdcf.clone(), crypt_filter)]),
stream_filter: stdcf.clone(),
string_filter: stdcf,
owner_password: &enc.owner_password,
user_password: &enc.user_password,
permissions,
}
}
PdfEncryptionAlgorithm::Aes256 => {
let crypt_filter: std::sync::Arc<dyn lopdf::encryption::crypt_filters::CryptFilter> =
std::sync::Arc::new(lopdf::encryption::crypt_filters::Aes256CryptFilter);
lopdf::EncryptionVersion::V5 {
encrypt_metadata: true,
crypt_filters: std::collections::BTreeMap::from([(stdcf.clone(), crypt_filter)]),
file_encryption_key,
stream_filter: stdcf.clone(),
string_filter: stdcf,
owner_password: &enc.owner_password,
user_password: &enc.user_password,
permissions,
}
}
}
}