pub mod cms;
pub mod cms_build;
pub mod der;
pub mod encode;
pub mod kari;
pub mod signed_data;
pub mod trust;
pub mod verify;
pub mod x509;
pub use encode::{
KariRecipient, PubSecCfGroup, PubSecEncoderConfig, PubSecEncryptionState, PubSecKariConfig,
PubSecMultiCfConfig, PubSecRecipient,
};
pub use trust::{CertRef, TrustStore};
use crate::decrypt::{CryptMethod, StandardHandler};
use crate::error::PdfError;
use crate::objects::{Dict, Object};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PubSecSubFilter {
Pkcs7S3,
Pkcs7S4,
Pkcs7S5V4 { aes: bool },
Pkcs7S5V5,
}
impl PubSecSubFilter {
fn from_dict(d: &Dict) -> Result<Self, PdfError> {
let lookup = |k: &str| {
d.entries()
.iter()
.find(|(kk, _)| kk == k)
.map(|(_, v)| v.clone())
};
let sub =
match lookup("SubFilter") {
Some(Object::Name(n)) => n,
_ => return Err(PdfError::other(
"PDF pubsec: /Encrypt missing /SubFilter (required for public-key handlers)",
)),
};
let v = match lookup("V") {
Some(Object::Integer(n)) => n,
_ => {
return Err(PdfError::other(
"PDF pubsec: /Encrypt missing /V (required)",
))
}
};
match (sub.as_str(), v) {
("adbe.pkcs7.s3", _) => Ok(Self::Pkcs7S3),
("adbe.pkcs7.s4", _) => Ok(Self::Pkcs7S4),
("adbe.pkcs7.s5", 4) => {
let aes = matches!(stmf_cfm(d).as_deref(), Some("AESV2"));
Ok(Self::Pkcs7S5V4 { aes })
}
("adbe.pkcs7.s5", 5) => Ok(Self::Pkcs7S5V5),
("adbe.pkcs7.s5", other) => Err(PdfError::other(format!(
"PDF pubsec: adbe.pkcs7.s5 with /V={other} not supported (V∈{{4,5}})"
))),
(other, _) => Err(PdfError::other(format!(
"PDF pubsec: SubFilter={other} not recognised"
))),
}
}
}
fn stmf_cfm(d: &Dict) -> Option<String> {
let entries = d.entries();
let stmf = entries
.iter()
.find(|(k, _)| k == "StmF")
.and_then(|(_, v)| {
if let Object::Name(s) = v {
Some(s.clone())
} else {
None
}
})?;
let cf = entries.iter().find(|(k, _)| k == "CF").and_then(|(_, v)| {
if let Object::Dict(d) = v {
Some(d.clone())
} else {
None
}
})?;
let filter = cf
.entries()
.iter()
.find(|(k, _)| k == &stmf)
.and_then(|(_, v)| {
if let Object::Dict(d) = v {
Some(d.clone())
} else {
None
}
})?;
filter
.entries()
.iter()
.find(|(k, _)| k == "CFM")
.and_then(|(_, v)| {
if let Object::Name(s) = v {
Some(s.clone())
} else {
None
}
})
}
pub struct PubSecCredential {
pub(crate) cert: x509::Certificate,
pub(crate) private_key: Option<rsa::RsaPrivateKey>,
pub(crate) ec_private: Option<(kari::KariCurve, Vec<u8>)>,
}
impl PubSecCredential {
pub fn from_der(cert_der: &[u8], pkcs8_der: &[u8]) -> Result<Self, PdfError> {
use rsa::pkcs8::DecodePrivateKey;
let cert = x509::Certificate::parse(cert_der)?;
let private_key = rsa::RsaPrivateKey::from_pkcs8_der(pkcs8_der)
.map_err(|e| PdfError::other(format!("PDF pubsec: RSA private key parse: {e}")))?;
Ok(Self {
cert,
private_key: Some(private_key),
ec_private: None,
})
}
#[doc(hidden)]
pub fn from_parsed(cert: x509::Certificate, private_key: rsa::RsaPrivateKey) -> Self {
Self {
cert,
private_key: Some(private_key),
ec_private: None,
}
}
pub fn from_parsed_ec_p256(cert: x509::Certificate, ec_private_scalar: Vec<u8>) -> Self {
Self::from_parsed_ec(cert, kari::KariCurve::P256, ec_private_scalar)
}
pub fn from_parsed_ec(
cert: x509::Certificate,
curve: kari::KariCurve,
ec_private_scalar: Vec<u8>,
) -> Self {
Self {
cert,
private_key: None,
ec_private: Some((curve, ec_private_scalar)),
}
}
pub fn with_ec_p256_scalar(self, ec_private_scalar: Vec<u8>) -> Self {
self.with_ec_scalar(kari::KariCurve::P256, ec_private_scalar)
}
pub fn with_ec_scalar(mut self, curve: kari::KariCurve, ec_private_scalar: Vec<u8>) -> Self {
self.ec_private = Some((curve, ec_private_scalar));
self
}
}
#[derive(Debug, Clone)]
pub struct PubSecMatch {
pub handler: StandardHandler,
pub permissions: Option<i32>,
pub crypt_filter_name: Option<String>,
}
pub fn open_with_certificate(
encrypt: &Dict,
credential: &PubSecCredential,
) -> Result<Option<StandardHandler>, PdfError> {
Ok(open_with_certificate_with_permissions(encrypt, credential)?.map(|m| m.handler))
}
pub fn open_with_certificate_and_trust_store(
encrypt: &Dict,
credential: &PubSecCredential,
trust_store: &TrustStore,
) -> Result<Option<StandardHandler>, PdfError> {
Ok(
open_with_certificate_and_trust_store_with_permissions(encrypt, credential, trust_store)?
.map(|m| m.handler),
)
}
pub fn open_with_certificate_and_trust_store_with_permissions(
encrypt: &Dict,
credential: &PubSecCredential,
trust_store: &TrustStore,
) -> Result<Option<PubSecMatch>, PdfError> {
open_inner(encrypt, credential, Some(trust_store))
}
pub fn open_with_certificate_with_permissions(
encrypt: &Dict,
credential: &PubSecCredential,
) -> Result<Option<PubSecMatch>, PdfError> {
open_inner(encrypt, credential, None)
}
fn open_inner(
encrypt: &Dict,
credential: &PubSecCredential,
trust_store: Option<&TrustStore>,
) -> Result<Option<PubSecMatch>, PdfError> {
let sub_filter = PubSecSubFilter::from_dict(encrypt)?;
let candidates = collect_recipient_arrays(encrypt)?;
if candidates.is_empty() {
return Err(PdfError::other(
"PDF pubsec: no /Recipients arrays found (document-level or per-CF)",
));
}
let encrypt_metadata = match encrypt
.entries()
.iter()
.find(|(k, _)| k == "EncryptMetadata")
{
Some((_, Object::Bool(b))) => *b,
_ => true,
};
for candidate in &candidates {
for blob in &candidate.blobs {
let envelope = cms::parse_envelope(blob)?;
let Some(plaintext) = try_unwrap(&envelope, credential, trust_store)? else {
continue;
};
if plaintext.len() < 20 {
return Err(PdfError::other(format!(
"PDF pubsec: enveloped content too short ({} < 20 bytes)",
plaintext.len()
)));
}
let seed = &plaintext[..20];
let permissions = if plaintext.len() >= 24 {
let p_bytes = &plaintext[20..24];
let p_arr: [u8; 4] = [p_bytes[0], p_bytes[1], p_bytes[2], p_bytes[3]];
let p = match sub_filter {
PubSecSubFilter::Pkcs7S5V5 => i32::from_be_bytes(p_arr),
_ => i32::from_le_bytes(p_arr),
};
Some(p)
} else {
None
};
let (method, revision) = candidate
.method_revision
.unwrap_or_else(|| default_method_revision(sub_filter));
let default_key_bits = match sub_filter {
PubSecSubFilter::Pkcs7S3 => 40,
PubSecSubFilter::Pkcs7S4 => 128,
PubSecSubFilter::Pkcs7S5V4 { .. } => 128,
PubSecSubFilter::Pkcs7S5V5 => 256,
};
let key_bits = candidate.key_length_bits.unwrap_or(default_key_bits);
let key = derive_file_key(
sub_filter,
seed,
&candidate.blobs,
encrypt_metadata,
key_bits,
);
return Ok(Some(PubSecMatch {
handler: StandardHandler {
key,
method,
revision,
},
permissions,
crypt_filter_name: candidate.cf_name.clone(),
}));
}
}
Ok(None)
}
fn default_method_revision(sub_filter: PubSecSubFilter) -> (CryptMethod, u8) {
match sub_filter {
PubSecSubFilter::Pkcs7S3 => (CryptMethod::Rc4, 2u8),
PubSecSubFilter::Pkcs7S4 => (CryptMethod::Rc4, 3),
PubSecSubFilter::Pkcs7S5V4 { aes } => (
if aes {
CryptMethod::Aes128
} else {
CryptMethod::Rc4
},
4,
),
PubSecSubFilter::Pkcs7S5V5 => (CryptMethod::Aes256, 6),
}
}
struct RecipientCandidate {
cf_name: Option<String>,
blobs: Vec<Vec<u8>>,
key_length_bits: Option<usize>,
method_revision: Option<(CryptMethod, u8)>,
}
fn collect_recipient_arrays(encrypt: &Dict) -> Result<Vec<RecipientCandidate>, PdfError> {
let lookup = |dict: &Dict, k: &str| {
dict.entries()
.iter()
.find(|(kk, _)| kk == k)
.map(|(_, v)| v.clone())
};
let sub = match lookup(encrypt, "SubFilter") {
Some(Object::Name(n)) => n,
_ => return Err(PdfError::other("PDF pubsec: /SubFilter required")),
};
let mut out: Vec<RecipientCandidate> = Vec::new();
if sub == "adbe.pkcs7.s5" {
let cf = match lookup(encrypt, "CF") {
Some(Object::Dict(d)) => d,
_ => return Err(PdfError::other("PDF pubsec: s5 requires /CF dictionary")),
};
let stmf_name: Option<String> = match lookup(encrypt, "StmF") {
Some(Object::Name(n)) => Some(n),
_ => None,
};
let mut entries = cf.entries().to_vec();
if let Some(name) = stmf_name.as_ref() {
if let Some(pos) = entries.iter().position(|(k, _)| k == name) {
let entry = entries.remove(pos);
entries.insert(0, entry);
}
}
for (name, entry) in &entries {
let Object::Dict(filter) = entry else {
continue;
};
let Some(recipients_obj) = lookup(filter, "Recipients") else {
continue;
};
let blobs = recipients_to_blobs(&recipients_obj)?;
if blobs.is_empty() {
continue;
}
let cfm = match lookup(filter, "CFM") {
Some(Object::Name(n)) => Some(n),
_ => None,
};
let length = match lookup(filter, "Length") {
Some(Object::Integer(n)) => Some(n as usize),
_ => None,
};
let method_revision = cfm.as_deref().and_then(|c| match c {
"V2" => Some((CryptMethod::Rc4, 4u8)),
"AESV2" => Some((CryptMethod::Aes128, 4u8)),
"AESV3" => Some((CryptMethod::Aes256, 6u8)),
_ => None,
});
let key_length_bits = match cfm.as_deref() {
Some("AESV2") => Some(128),
Some("AESV3") => Some(256),
_ => length.map(|len| {
len * 8
}),
};
out.push(RecipientCandidate {
cf_name: Some(name.clone()),
blobs,
key_length_bits,
method_revision,
});
}
if let Some(top) = lookup(encrypt, "Recipients") {
let blobs = recipients_to_blobs(&top)?;
let already = out.iter().any(|c| c.blobs == blobs);
if !already && !blobs.is_empty() {
out.push(RecipientCandidate {
cf_name: None,
blobs,
key_length_bits: None,
method_revision: None,
});
}
}
} else {
let top = lookup(encrypt, "Recipients").ok_or_else(|| {
PdfError::other("PDF pubsec: /Recipients missing for s3/s4 SubFilter")
})?;
let blobs = recipients_to_blobs(&top)?;
out.push(RecipientCandidate {
cf_name: None,
blobs,
key_length_bits: None,
method_revision: None,
});
}
Ok(out)
}
fn recipients_to_blobs(array: &Object) -> Result<Vec<Vec<u8>>, PdfError> {
match array {
Object::Array(items) => items
.iter()
.map(|item| match item {
Object::LiteralString(s) | Object::HexString(s) => Ok(s.clone()),
other => Err(PdfError::other(format!(
"PDF pubsec: /Recipients element must be a string (got {other:?})"
))),
})
.collect(),
Object::LiteralString(s) | Object::HexString(s) => Ok(vec![s.clone()]),
other => Err(PdfError::other(format!(
"PDF pubsec: /Recipients must be an array of strings (got {other:?})"
))),
}
}
fn try_unwrap(
envelope: &cms::EnvelopedData,
credential: &PubSecCredential,
trust_store: Option<&TrustStore>,
) -> Result<Option<Vec<u8>>, PdfError> {
let our_issuer = &credential.cert.issuer_der;
let our_serial = &credential.cert.serial;
let our_ski = credential.cert.subject_key_identifier();
for variant in &envelope.all_recipients {
match variant {
cms::RecipientInfoVariant::KeyTrans(recipient) => {
let matched = match &recipient.rid {
cms::RecipientId::IssuerAndSerial(ias) => {
&ias.issuer_der == our_issuer && &ias.serial == our_serial
}
cms::RecipientId::SubjectKeyIdentifier(ski) => match &our_ski {
Some(our) => ski == our,
None => false,
},
};
if !matched {
continue;
}
let Some(rsa_key) = credential.private_key.as_ref() else {
continue;
};
let cek = rsa_key
.decrypt(rsa::Pkcs1v15Encrypt, &recipient.encrypted_key)
.map_err(|e| PdfError::other(format!("PDF pubsec: RSA decrypt failed: {e}")))?;
let plaintext = decrypt_envelope_content(
&envelope.content_encryption,
&cek,
&envelope.encrypted_content,
)?;
return Ok(Some(plaintext));
}
cms::RecipientInfoVariant::KeyAgree(kari) => {
let Some((curve, ec_scalar)) = credential.ec_private.as_ref() else {
continue;
};
let Some(kdf) = kari::KariKdf::from_kea_oid(&kari.key_encryption_oid) else {
continue;
};
if !kdf.is_valid_for(*curve) {
continue;
}
let Some(slot) = kari::match_kari_slot(
kari,
our_issuer,
our_serial,
credential.cert.spki_pubkey_bits.as_deref(),
) else {
continue;
};
let recipient = kari::EcRecipient {
curve: *curve,
private_scalar: ec_scalar.clone(),
public_point_sec1: credential.cert.spki_pubkey_bits.clone().unwrap_or_default(),
};
let cek = kari::unwrap_kari_with_trust_store(kari, slot, &recipient, trust_store)?;
let plaintext = decrypt_envelope_content(
&envelope.content_encryption,
&cek,
&envelope.encrypted_content,
)?;
return Ok(Some(plaintext));
}
}
}
Ok(None)
}
fn decrypt_envelope_content(
alg: &cms::ContentEncryption,
cek: &[u8],
ciphertext: &[u8],
) -> Result<Vec<u8>, PdfError> {
match alg {
cms::ContentEncryption::Rc4 => Ok(crate::decrypt::rc4(cek, ciphertext)),
cms::ContentEncryption::Aes128Cbc { iv } => {
if cek.len() != 16 {
return Err(PdfError::other(format!(
"PDF pubsec: AES-128 CEK must be 16 bytes (got {})",
cek.len()
)));
}
aes_cbc_decrypt::<aes::Aes128>(cek, iv, ciphertext)
}
cms::ContentEncryption::Aes256Cbc { iv } => {
if cek.len() != 32 {
return Err(PdfError::other(format!(
"PDF pubsec: AES-256 CEK must be 32 bytes (got {})",
cek.len()
)));
}
aes_cbc_decrypt::<aes::Aes256>(cek, iv, ciphertext)
}
cms::ContentEncryption::Rc2Cbc {
effective_key_bits,
iv,
} => rc2_cbc_decrypt(cek, *effective_key_bits, iv, ciphertext),
cms::ContentEncryption::DesEde3Cbc { iv } => des_ede3_cbc_decrypt(cek, iv, ciphertext),
}
}
fn aes_cbc_decrypt<C>(key: &[u8], iv: &[u8; 16], ct: &[u8]) -> Result<Vec<u8>, PdfError>
where
C: aes::cipher::BlockCipher
+ aes::cipher::BlockEncrypt
+ aes::cipher::BlockDecrypt
+ aes::cipher::KeyInit
+ aes::cipher::BlockSizeUser<BlockSize = aes::cipher::consts::U16>,
{
use aes::cipher::{BlockDecryptMut, KeyIvInit};
type Dec<C> = cbc::Decryptor<C>;
if ct.len() % 16 != 0 {
return Err(PdfError::other(format!(
"PDF pubsec: AES-CBC ciphertext {} not block-aligned",
ct.len()
)));
}
let dec = <Dec<C> as KeyIvInit>::new_from_slices(key, iv)
.map_err(|e| PdfError::other(format!("PDF pubsec: AES init failed: {e}")))?;
let mut buf = ct.to_vec();
let pt = dec
.decrypt_padded_mut::<aes::cipher::block_padding::Pkcs7>(&mut buf)
.map_err(|e| PdfError::other(format!("PDF pubsec: AES-CBC unpad: {e:?}")))?;
Ok(pt.to_vec())
}
fn rc2_cbc_decrypt(
cek: &[u8],
effective_key_bits: u32,
iv: &[u8; 8],
ct: &[u8],
) -> Result<Vec<u8>, PdfError> {
use cbc::cipher::{BlockDecryptMut, InnerIvInit};
use rc2::Rc2;
if ct.len() % 8 != 0 {
return Err(PdfError::other(format!(
"PDF pubsec: RC2-CBC ciphertext {} not block-aligned (8-byte blocks)",
ct.len()
)));
}
if cek.is_empty() || cek.len() > 128 {
return Err(PdfError::other(format!(
"PDF pubsec: RC2 CEK length {} out of RFC 2268 range (1..=128 bytes)",
cek.len()
)));
}
let cipher = Rc2::new_with_eff_key_len(cek, effective_key_bits as usize);
let dec = cbc::Decryptor::<Rc2>::inner_iv_slice_init(cipher, iv)
.map_err(|e| PdfError::other(format!("PDF pubsec: RC2-CBC IV init failed: {e}")))?;
let mut buf = ct.to_vec();
let pt = dec
.decrypt_padded_mut::<cbc::cipher::block_padding::Pkcs7>(&mut buf)
.map_err(|e| PdfError::other(format!("PDF pubsec: RC2-CBC unpad: {e:?}")))?;
Ok(pt.to_vec())
}
fn des_ede3_cbc_decrypt(cek: &[u8], iv: &[u8; 8], ct: &[u8]) -> Result<Vec<u8>, PdfError> {
use cbc::cipher::{BlockDecryptMut, KeyIvInit};
use des::TdesEde3;
if ct.len() % 8 != 0 {
return Err(PdfError::other(format!(
"PDF pubsec: 3DES-CBC ciphertext {} not block-aligned (8-byte blocks)",
ct.len()
)));
}
if cek.len() != 24 {
return Err(PdfError::other(format!(
"PDF pubsec: 3DES (TdesEde3) CEK must be 24 bytes (got {})",
cek.len()
)));
}
type Dec = cbc::Decryptor<TdesEde3>;
let dec = <Dec as KeyIvInit>::new_from_slices(cek, iv)
.map_err(|e| PdfError::other(format!("PDF pubsec: 3DES-CBC init failed: {e}")))?;
let mut buf = ct.to_vec();
let pt = dec
.decrypt_padded_mut::<cbc::cipher::block_padding::Pkcs7>(&mut buf)
.map_err(|e| PdfError::other(format!("PDF pubsec: 3DES-CBC unpad: {e:?}")))?;
Ok(pt.to_vec())
}
fn derive_file_key(
sub: PubSecSubFilter,
seed: &[u8],
recipients_blobs: &[Vec<u8>],
encrypt_metadata: bool,
key_length_bits: usize,
) -> Vec<u8> {
let n = key_length_bits / 8;
let mut input =
Vec::with_capacity(20 + recipients_blobs.iter().map(|v| v.len()).sum::<usize>() + 4);
input.extend_from_slice(seed);
for blob in recipients_blobs {
input.extend_from_slice(blob);
}
if !encrypt_metadata {
input.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]);
}
let digest: Vec<u8> = match sub {
PubSecSubFilter::Pkcs7S5V5 => {
use sha2::Digest;
sha2::Sha256::digest(&input).to_vec()
}
_ => {
use sha1::Digest;
sha1::Sha1::digest(&input).to_vec()
}
};
digest[..n.min(digest.len())].to_vec()
}
#[cfg(test)]
mod tests {
use super::cms_build::{
build_envelope_aes128, build_envelope_aes256, build_envelope_rc4, rsa_pkcs1_encrypt,
RecipientPlain,
};
use super::*;
use crate::objects::Dict;
fn rsa_keypair() -> (rsa::RsaPrivateKey, rsa::RsaPublicKey) {
let mut rng = rsa::rand_core::OsRng;
let priv_key = rsa::RsaPrivateKey::new(&mut rng, 2048).expect("RSA keypair");
let pub_key = rsa::RsaPublicKey::from(&priv_key);
(priv_key, pub_key)
}
fn fake_cert(issuer: &[u8], serial: &[u8]) -> super::x509::Certificate {
super::x509::Certificate {
issuer_der: issuer.to_vec(),
serial: serial.to_vec(),
spki_pubkey_bits: None,
validity: None,
spki_algorithm_oid: None,
spki_algorithm_params: None,
}
}
fn make_encrypt_dict(sub_filter: &str, v: i64, recipients: &[Vec<u8>]) -> Dict {
let mut d = Dict::default();
d.set("Filter", Object::Name("Adobe.PPKLite".into()));
d.set("SubFilter", Object::Name(sub_filter.into()));
d.set("V", Object::Integer(v));
d.set("P", Object::Integer(-4));
let arr = recipients
.iter()
.map(|r| Object::LiteralString(r.clone()))
.collect();
d.set("Recipients", Object::Array(arr));
d
}
#[test]
fn s4_open_round_trip() {
let (priv_key, pub_key) = rsa_keypair();
let issuer_der = super::der::write_sequence(b"O=Test");
let serial = vec![0x01, 0x42];
let cek = [0x66u8; 16];
let mut plaintext = vec![0u8; 24];
plaintext[..20].copy_from_slice(&[0xAB; 20]);
plaintext[20..24].copy_from_slice(&((-4i32) as u32).to_le_bytes());
let encrypted_key = rsa_pkcs1_encrypt(&pub_key, &cek).unwrap();
let envelope_der = build_envelope_rc4(
&[RecipientPlain::ias(
issuer_der.clone(),
serial.clone(),
encrypted_key,
)],
&plaintext,
&cek,
);
let credential = PubSecCredential::from_parsed(fake_cert(&issuer_der, &serial), priv_key);
let encrypt = make_encrypt_dict("adbe.pkcs7.s4", 2, &[envelope_der]);
let handler = open_with_certificate(&encrypt, &credential)
.expect("open ok")
.expect("matched recipient");
assert_eq!(handler.method, CryptMethod::Rc4);
assert_eq!(handler.revision, 3);
assert_eq!(handler.key.len(), 16);
}
#[test]
fn s5_v5_aes256_round_trip() {
let (priv_key, pub_key) = rsa_keypair();
let issuer_der = super::der::write_sequence(b"O=Test");
let serial = vec![0x42, 0x01, 0x00];
let cek = [0xC1u8; 32];
let iv = [0xCAu8; 16];
let mut plaintext = vec![0u8; 24];
plaintext[..20].copy_from_slice(&[0xCD; 20]);
plaintext[20..24].copy_from_slice(&[0xFF, 0xFF, 0xFF, 0xFC]);
let encrypted_key = rsa_pkcs1_encrypt(&pub_key, &cek).unwrap();
let envelope_der = build_envelope_aes256(
&[RecipientPlain::ias(
issuer_der.clone(),
serial.clone(),
encrypted_key,
)],
&plaintext,
&cek,
&iv,
);
let mut filter = Dict::default();
filter.set("CFM", Object::Name("AESV3".into()));
filter.set(
"Recipients",
Object::Array(vec![Object::LiteralString(envelope_der)]),
);
filter.set("Length", Object::Integer(32));
let mut cf = Dict::default();
cf.set("DefaultCryptFilter", Object::Dict(filter));
let mut encrypt = Dict::default();
encrypt.set("Filter", Object::Name("Adobe.PPKLite".into()));
encrypt.set("SubFilter", Object::Name("adbe.pkcs7.s5".into()));
encrypt.set("V", Object::Integer(5));
encrypt.set("P", Object::Integer(-4));
encrypt.set("StmF", Object::Name("DefaultCryptFilter".into()));
encrypt.set("StrF", Object::Name("DefaultCryptFilter".into()));
encrypt.set("CF", Object::Dict(cf));
let credential = PubSecCredential::from_parsed(fake_cert(&issuer_der, &serial), priv_key);
let handler = open_with_certificate(&encrypt, &credential)
.expect("open ok")
.expect("matched recipient");
assert_eq!(handler.method, CryptMethod::Aes256);
assert_eq!(handler.revision, 6);
assert_eq!(handler.key.len(), 32);
}
#[test]
fn open_returns_none_when_cert_does_not_match() {
let (priv_key, pub_key) = rsa_keypair();
let issuer_der = super::der::write_sequence(b"O=Other");
let cek = [0u8; 32];
let iv = [0u8; 16];
let plaintext = vec![0xAA; 24];
let encrypted_key = rsa_pkcs1_encrypt(&pub_key, &cek).unwrap();
let envelope_der = build_envelope_aes256(
&[RecipientPlain::ias(
issuer_der.clone(),
vec![0x01],
encrypted_key,
)],
&plaintext,
&cek,
&iv,
);
let mut filter = Dict::default();
filter.set("CFM", Object::Name("AESV3".into()));
filter.set(
"Recipients",
Object::Array(vec![Object::LiteralString(envelope_der)]),
);
let mut cf = Dict::default();
cf.set("F", Object::Dict(filter));
let mut encrypt = Dict::default();
encrypt.set("Filter", Object::Name("Adobe.PPKLite".into()));
encrypt.set("SubFilter", Object::Name("adbe.pkcs7.s5".into()));
encrypt.set("V", Object::Integer(5));
encrypt.set("P", Object::Integer(-4));
encrypt.set("StmF", Object::Name("F".into()));
encrypt.set("StrF", Object::Name("F".into()));
encrypt.set("CF", Object::Dict(cf));
let credential = PubSecCredential::from_parsed(
fake_cert(&issuer_der, &[0x99]), priv_key,
);
let handler = open_with_certificate(&encrypt, &credential).unwrap();
assert!(handler.is_none(), "unexpected match: {handler:?}");
}
#[test]
fn s5_v4_aes128_round_trip() {
let (priv_key, pub_key) = rsa_keypair();
let issuer_der = super::der::write_sequence(b"O=v4test");
let serial = vec![0x05];
let cek = [0x77u8; 16];
let iv = [0x88u8; 16];
let plaintext = vec![0u8; 24];
let encrypted_key = rsa_pkcs1_encrypt(&pub_key, &cek).unwrap();
let envelope_der = build_envelope_aes128(
&[RecipientPlain::ias(
issuer_der.clone(),
serial.clone(),
encrypted_key,
)],
&plaintext,
&cek,
&iv,
);
let mut filter = Dict::default();
filter.set("CFM", Object::Name("AESV2".into()));
filter.set(
"Recipients",
Object::Array(vec![Object::LiteralString(envelope_der)]),
);
let mut cf = Dict::default();
cf.set("DefaultCryptFilter", Object::Dict(filter));
let mut encrypt = Dict::default();
encrypt.set("Filter", Object::Name("Adobe.PPKLite".into()));
encrypt.set("SubFilter", Object::Name("adbe.pkcs7.s5".into()));
encrypt.set("V", Object::Integer(4));
encrypt.set("P", Object::Integer(-4));
encrypt.set("StmF", Object::Name("DefaultCryptFilter".into()));
encrypt.set("StrF", Object::Name("DefaultCryptFilter".into()));
encrypt.set("CF", Object::Dict(cf));
let credential = PubSecCredential::from_parsed(fake_cert(&issuer_der, &serial), priv_key);
let handler = open_with_certificate(&encrypt, &credential)
.expect("open ok")
.expect("matched recipient");
assert_eq!(handler.method, CryptMethod::Aes128);
assert_eq!(handler.revision, 4);
assert_eq!(handler.key.len(), 16);
}
}