use oxideav_pdf::decrypt::{md5, rc4, CryptMethod, StandardHandler};
use oxideav_pdf::pubsec::{
cms::{OID_AES128_CBC, OID_AES256_CBC, OID_DATA, OID_ENVELOPED_DATA, OID_RSA_ENCRYPTION},
der,
x509::Certificate,
PubSecCredential,
};
use oxideav_pdf::read_pdf_to_scene_with_certificate;
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 keygen");
let pub_key = rsa::RsaPublicKey::from(&priv_key);
(priv_key, pub_key)
}
fn rsa_pkcs1_encrypt(pub_key: &rsa::RsaPublicKey, data: &[u8]) -> Vec<u8> {
let mut rng = rsa::rand_core::OsRng;
pub_key
.encrypt(&mut rng, rsa::Pkcs1v15Encrypt, data)
.expect("RSA encrypt")
}
fn aes_cbc_encrypt_pkcs7(key: &[u8], iv: &[u8; 16], data: &[u8]) -> Vec<u8> {
use aes::cipher::{BlockEncryptMut, KeyIvInit};
let pad_block = (data.len() / 16) + 1;
let mut buf = vec![0u8; pad_block * 16];
if key.len() == 16 {
type E = cbc::Encryptor<aes::Aes128>;
let n = E::new(key.into(), iv.into())
.encrypt_padded_b2b_mut::<aes::cipher::block_padding::Pkcs7>(data, &mut buf)
.unwrap()
.len();
buf.truncate(n);
} else if key.len() == 32 {
type E = cbc::Encryptor<aes::Aes256>;
let n = E::new(key.into(), iv.into())
.encrypt_padded_b2b_mut::<aes::cipher::block_padding::Pkcs7>(data, &mut buf)
.unwrap()
.len();
buf.truncate(n);
} else {
panic!("unsupported key size");
}
buf
}
fn build_envelope(
issuer_der: &[u8],
serial: &[u8],
pub_key: &rsa::RsaPublicKey,
cek: &[u8],
iv: &[u8; 16],
plaintext: &[u8],
aes_oid: &[u64],
) -> Vec<u8> {
let encrypted_key = rsa_pkcs1_encrypt(pub_key, cek);
let serial_int = der::write_integer_bytes(serial);
let ias_body = {
let mut b = Vec::with_capacity(issuer_der.len() + serial_int.len());
b.extend_from_slice(issuer_der);
b.extend_from_slice(&serial_int);
b
};
let ias = der::write_sequence(&ias_body);
let kea = der::write_sequence(&{
let mut b = der::write_oid(&OID_RSA_ENCRYPTION);
b.extend_from_slice(&der::write_null());
b
});
let ktri = der::write_sequence(&{
let mut b = der::write_integer_u64(0);
b.extend_from_slice(&ias);
b.extend_from_slice(&kea);
b.extend_from_slice(&der::write_octet_string(&encrypted_key));
b
});
let ri_set = der::write_set(&ktri);
let encrypted_content = aes_cbc_encrypt_pkcs7(cek, iv, plaintext);
let alg_id = der::write_sequence(&{
let mut b = der::write_oid(aes_oid);
b.extend_from_slice(&der::write_octet_string(iv));
b
});
let eci = der::write_sequence(&{
let mut b = der::write_oid(&OID_DATA);
b.extend_from_slice(&alg_id);
b.extend_from_slice(&der::write_context_primitive(0, &encrypted_content));
b
});
let enveloped = der::write_sequence(&{
let mut b = der::write_integer_u64(0);
b.extend_from_slice(&ri_set);
b.extend_from_slice(&eci);
b
});
der::write_sequence(&{
let mut b = der::write_oid(&OID_ENVELOPED_DATA);
b.extend_from_slice(&der::write_context_constructed(0, &enveloped));
b
})
}
fn build_envelope_rc4(
issuer_der: &[u8],
serial: &[u8],
pub_key: &rsa::RsaPublicKey,
cek: &[u8],
plaintext: &[u8],
) -> Vec<u8> {
let rc4_oid = [1u64, 2, 840, 113549, 3, 4];
let encrypted_key = rsa_pkcs1_encrypt(pub_key, cek);
let serial_int = der::write_integer_bytes(serial);
let ias_body = {
let mut b = Vec::with_capacity(issuer_der.len() + serial_int.len());
b.extend_from_slice(issuer_der);
b.extend_from_slice(&serial_int);
b
};
let ias = der::write_sequence(&ias_body);
let kea = der::write_sequence(&{
let mut b = der::write_oid(&OID_RSA_ENCRYPTION);
b.extend_from_slice(&der::write_null());
b
});
let ktri = der::write_sequence(&{
let mut b = der::write_integer_u64(0);
b.extend_from_slice(&ias);
b.extend_from_slice(&kea);
b.extend_from_slice(&der::write_octet_string(&encrypted_key));
b
});
let ri_set = der::write_set(&ktri);
let encrypted_content = rc4(cek, plaintext);
let alg_id = der::write_sequence(&{
let mut b = der::write_oid(&rc4_oid);
b.extend_from_slice(&der::write_null());
b
});
let eci = der::write_sequence(&{
let mut b = der::write_oid(&OID_DATA);
b.extend_from_slice(&alg_id);
b.extend_from_slice(&der::write_context_primitive(0, &encrypted_content));
b
});
let enveloped = der::write_sequence(&{
let mut b = der::write_integer_u64(0);
b.extend_from_slice(&ri_set);
b.extend_from_slice(&eci);
b
});
der::write_sequence(&{
let mut b = der::write_oid(&OID_ENVELOPED_DATA);
b.extend_from_slice(&der::write_context_constructed(0, &enveloped));
b
})
}
fn derive_file_key_sha1(seed: &[u8], recipients: &[Vec<u8>], n: usize) -> Vec<u8> {
use sha1::Digest;
let mut input = Vec::new();
input.extend_from_slice(seed);
for blob in recipients {
input.extend_from_slice(blob);
}
let h = sha1::Sha1::digest(&input);
h[..n].to_vec()
}
fn derive_file_key_sha256(seed: &[u8], recipients: &[Vec<u8>], n: usize) -> Vec<u8> {
use sha2::Digest;
let mut input = Vec::new();
input.extend_from_slice(seed);
for blob in recipients {
input.extend_from_slice(blob);
}
let h = sha2::Sha256::digest(&input);
h[..n].to_vec()
}
fn encrypt_with_object_key(handler: &StandardHandler, id: u32, data: &[u8]) -> Vec<u8> {
use aes::cipher::{BlockEncryptMut, KeyIvInit};
type Aes128CbcEnc = cbc::Encryptor<aes::Aes128>;
let key = handler.key.clone();
let n = key.len();
if handler.method == CryptMethod::Aes256 {
let iv = [0x55u8; 16];
let pad_block = (data.len() / 16) + 1;
let mut buf = vec![0u8; pad_block * 16];
type Aes256CbcEnc = cbc::Encryptor<aes::Aes256>;
let bytes = Aes256CbcEnc::new((&key[..]).into(), (&iv).into())
.encrypt_padded_b2b_mut::<aes::cipher::block_padding::Pkcs7>(data, &mut buf)
.unwrap()
.len();
buf.truncate(bytes);
let mut full = Vec::with_capacity(16 + bytes);
full.extend_from_slice(&iv);
full.extend_from_slice(&buf);
return full;
}
let mut buf = Vec::with_capacity(n + 9);
buf.extend_from_slice(&key);
buf.push((id & 0xFF) as u8);
buf.push(((id >> 8) & 0xFF) as u8);
buf.push(((id >> 16) & 0xFF) as u8);
buf.push(0); buf.push(0); let aes_mode = handler.method == CryptMethod::Aes128;
if aes_mode {
buf.extend_from_slice(&[0x73, 0x41, 0x6C, 0x54]);
}
let h = md5(&buf);
let take = (n + 5).min(16);
let obj_key = &h[..take];
if aes_mode {
let iv = [0x42u8; 16];
let enc = Aes128CbcEnc::new(obj_key.into(), (&iv).into());
let mut out = vec![0u8; ((data.len() / 16) + 1) * 16];
let bytes = enc
.encrypt_padded_b2b_mut::<aes::cipher::block_padding::Pkcs7>(data, &mut out)
.unwrap()
.len();
out.truncate(bytes);
let mut full = Vec::with_capacity(16 + bytes);
full.extend_from_slice(&iv);
full.extend_from_slice(&out);
full
} else {
rc4(obj_key, data)
}
}
#[derive(Clone, Copy)]
enum Profile {
PkS4,
PkS5V4Aes128,
PkS5V5Aes256,
}
fn build_pubsec_pdf(profile: Profile, title: &str) -> (Vec<u8>, PubSecCredential) {
let (priv_key, pub_key) = rsa_keypair();
let issuer_der = der::write_sequence(b"O=OxideAV pubsec test");
let serial = vec![0x10, 0x20, 0x30];
let (sub_filter, v, length_bits, alg_oid, method, revision) = match profile {
Profile::PkS4 => ("adbe.pkcs7.s4", 2, 128, None, CryptMethod::Rc4, 3u8),
Profile::PkS5V4Aes128 => (
"adbe.pkcs7.s5",
4,
128,
Some(OID_AES128_CBC),
CryptMethod::Aes128,
4,
),
Profile::PkS5V5Aes256 => (
"adbe.pkcs7.s5",
5,
256,
Some(OID_AES256_CBC),
CryptMethod::Aes256,
6,
),
};
let cek_bytes: Vec<u8> = match profile {
Profile::PkS4 => vec![0xA1u8; 16], Profile::PkS5V4Aes128 => vec![0xB2u8; 16],
Profile::PkS5V5Aes256 => vec![0xC3u8; 32],
};
let env_iv = [0x77u8; 16];
let seed = [0x5Au8; 20];
let mut plaintext = Vec::with_capacity(24);
plaintext.extend_from_slice(&seed);
plaintext.extend_from_slice(&((-4i32) as u32).to_le_bytes());
let envelope = match profile {
Profile::PkS4 => build_envelope_rc4(&issuer_der, &serial, &pub_key, &cek_bytes, &plaintext),
_ => build_envelope(
&issuer_der,
&serial,
&pub_key,
&cek_bytes,
&env_iv,
&plaintext,
&alg_oid.unwrap(),
),
};
let file_key = if matches!(profile, Profile::PkS5V5Aes256) {
derive_file_key_sha256(&seed, std::slice::from_ref(&envelope), length_bits / 8)
} else {
derive_file_key_sha1(&seed, std::slice::from_ref(&envelope), length_bits / 8)
};
let handler = StandardHandler {
key: file_key.clone(),
method,
revision,
};
let info_title_str = encrypt_with_object_key(&handler, 5, title.as_bytes());
let content_plain = b"q\n1 0 0 rg\n10 10 50 50 re\nf\nQ\n".to_vec();
let content_cipher = encrypt_with_object_key(&handler, 4, &content_plain);
let mut bytes = Vec::with_capacity(4096);
let header = if v >= 4 {
b"%PDF-1.6\n%\xE2\xE3\xCF\xD3\n".as_slice()
} else {
b"%PDF-1.4\n%\xE2\xE3\xCF\xD3\n".as_slice()
};
bytes.extend_from_slice(header);
let mut offsets = [0u64; 7];
offsets[1] = bytes.len() as u64;
bytes.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
offsets[2] = bytes.len() as u64;
bytes.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n");
offsets[3] = bytes.len() as u64;
bytes.extend_from_slice(
b"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] /Contents 4 0 R >>\nendobj\n",
);
offsets[4] = bytes.len() as u64;
bytes.extend_from_slice(
format!("4 0 obj\n<< /Length {} >>\nstream\n", content_cipher.len()).as_bytes(),
);
bytes.extend_from_slice(&content_cipher);
bytes.extend_from_slice(b"\nendstream\nendobj\n");
offsets[5] = bytes.len() as u64;
bytes.extend_from_slice(b"5 0 obj\n<< /Title <");
for b in &info_title_str {
bytes.extend_from_slice(format!("{:02X}", b).as_bytes());
}
bytes.extend_from_slice(b"> >>\nendobj\n");
offsets[6] = bytes.len() as u64;
let recipients_hex: String = {
let mut s = String::new();
for byte in &envelope {
s.push_str(&format!("{:02X}", byte));
}
s
};
let encrypt_dict = match profile {
Profile::PkS4 => format!(
"6 0 obj\n<< /Filter /Adobe.PPKLite /SubFilter /{sub_filter} /V {v} /R {revision} \
/Length {length_bits} /P -4 /Recipients [<{recipients}>] >>\nendobj\n",
sub_filter = sub_filter,
recipients = recipients_hex,
),
Profile::PkS5V4Aes128 => format!(
"6 0 obj\n<< /Filter /Adobe.PPKLite /SubFilter /{sub_filter} /V {v} /R {revision} \
/Length {length_bits} /P -4 \
/CF << /DefaultCryptFilter << /CFM /AESV2 /Length 16 /Recipients [<{recipients}>] >> >> \
/StmF /DefaultCryptFilter /StrF /DefaultCryptFilter \
/Recipients [<{recipients}>] >>\nendobj\n",
sub_filter = sub_filter,
recipients = recipients_hex,
),
Profile::PkS5V5Aes256 => format!(
"6 0 obj\n<< /Filter /Adobe.PPKLite /SubFilter /{sub_filter} /V {v} /R {revision} \
/Length {length_bits} /P -4 \
/CF << /DefaultCryptFilter << /CFM /AESV3 /Length 32 /Recipients [<{recipients}>] >> >> \
/StmF /DefaultCryptFilter /StrF /DefaultCryptFilter \
/Recipients [<{recipients}>] >>\nendobj\n",
sub_filter = sub_filter,
recipients = recipients_hex,
),
};
bytes.extend_from_slice(encrypt_dict.as_bytes());
let xref_off = bytes.len() as u64;
bytes.extend_from_slice(b"xref\n0 7\n");
bytes.extend_from_slice(b"0000000000 65535 f \n");
for &off in &offsets[1..7] {
bytes.extend_from_slice(format!("{:010} 00000 n \n", off).as_bytes());
}
let file_id = b"OXIDEAV-PUBSEC-FILE-ID-0123456!".to_vec();
bytes.extend_from_slice(b"trailer\n<< /Size 7 /Root 1 0 R /Info 5 0 R /Encrypt 6 0 R /ID [<");
for b in &file_id {
bytes.extend_from_slice(format!("{:02X}", b).as_bytes());
}
bytes.extend_from_slice(b"> <");
for b in &file_id {
bytes.extend_from_slice(format!("{:02X}", b).as_bytes());
}
bytes.extend_from_slice(b">] >>\n");
bytes.extend_from_slice(format!("startxref\n{}\n%%EOF\n", xref_off).as_bytes());
let cert = Certificate {
issuer_der,
serial,
spki_pubkey_bits: None,
validity: None,
..Default::default()
};
let credential = PubSecCredential::from_parsed(cert, priv_key);
(bytes, credential)
}
#[test]
fn s4_rc4_128_decodes_with_certificate() {
let (pdf, cred) = build_pubsec_pdf(Profile::PkS4, "PubSec S4 Title");
let scene = read_pdf_to_scene_with_certificate(&pdf, &cred).expect("decrypt s4");
assert_eq!(scene.metadata.title.as_deref(), Some("PubSec S4 Title"));
let pages = scene.pages.expect("pages");
assert_eq!(pages.len(), 1);
assert_eq!(pages[0].width, 100.0);
assert_eq!(pages[0].height, 100.0);
}
#[test]
fn s5_v4_aes128_decodes_with_certificate() {
let (pdf, cred) = build_pubsec_pdf(Profile::PkS5V4Aes128, "S5/V4 AES-128");
let scene = read_pdf_to_scene_with_certificate(&pdf, &cred).expect("decrypt s5/v4");
assert_eq!(scene.metadata.title.as_deref(), Some("S5/V4 AES-128"));
}
#[test]
fn s5_v5_aes256_decodes_with_certificate() {
let (pdf, cred) = build_pubsec_pdf(Profile::PkS5V5Aes256, "S5/V5 AES-256");
let scene = read_pdf_to_scene_with_certificate(&pdf, &cred).expect("decrypt s5/v5");
assert_eq!(scene.metadata.title.as_deref(), Some("S5/V5 AES-256"));
}
#[test]
fn wrong_certificate_serial_returns_error() {
let (pdf, _correct) = build_pubsec_pdf(Profile::PkS5V5Aes256, "Owner-only");
let (priv_key, _pub) = rsa_keypair();
let bad_cred = PubSecCredential::from_parsed(
Certificate {
issuer_der: der::write_sequence(b"O=OxideAV pubsec test"),
serial: vec![0xFF, 0xFF, 0xFF],
spki_pubkey_bits: None,
validity: None,
..Default::default()
},
priv_key,
);
let err = read_pdf_to_scene_with_certificate(&pdf, &bad_cred).unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("certificate did not match"),
"expected wrong-cert error, got: {msg}"
);
}
fn one_page_scene(title: &str) -> oxideav_scene::Scene {
use oxideav_core::vector::{
FillRule, Group, Node, Paint, Path, PathCommand, PathNode, Point, Rgba, VectorFrame,
};
use oxideav_core::TimeBase;
use oxideav_scene::{Metadata, Page, Scene};
let mut p = Path::new();
p.commands.push(PathCommand::MoveTo(Point::new(10.0, 10.0)));
p.commands.push(PathCommand::LineTo(Point::new(60.0, 10.0)));
p.commands.push(PathCommand::LineTo(Point::new(60.0, 60.0)));
p.commands.push(PathCommand::LineTo(Point::new(10.0, 60.0)));
p.commands.push(PathCommand::Close);
let frame = VectorFrame {
width: 100.0,
height: 100.0,
view_box: None,
root: Group {
children: vec![Node::Path(PathNode {
path: p,
fill: Some(Paint::Solid(Rgba::opaque(0xFF, 0x00, 0x00))),
stroke: None,
fill_rule: FillRule::NonZero,
})],
..Group::default()
},
pts: None,
time_base: TimeBase::new(1, 1),
};
let mut page = Page::new(100.0, 100.0);
page.content = frame;
Scene {
pages: Some(vec![page]),
metadata: Metadata {
title: Some(title.into()),
..Metadata::default()
},
..Scene::default()
}
}
fn writer_recipient_for_keypair(
issuer: &[u8],
serial: Vec<u8>,
pub_key: &rsa::RsaPublicKey,
) -> oxideav_pdf::PubSecRecipient {
oxideav_pdf::PubSecRecipient::from_issuer_and_serial(
der::write_sequence(issuer),
serial,
pub_key.clone(),
)
}
#[test]
fn writer_s4_then_reader_round_trip() {
let (priv_key, pub_key) = rsa_keypair();
let issuer = b"O=writer-s4";
let serial = vec![0x10];
let recipient = writer_recipient_for_keypair(issuer, serial.clone(), &pub_key);
let cfg = oxideav_pdf::PubSecEncoderConfig::pkcs7_s4(vec![recipient]);
let scene = one_page_scene("WriterS4");
let pdf = oxideav_pdf::write_pdf_from_scene_pubsec_encrypted(&scene, &cfg).expect("encode");
let cred = PubSecCredential::from_parsed(
Certificate {
issuer_der: der::write_sequence(issuer),
serial,
spki_pubkey_bits: None,
validity: None,
..Default::default()
},
priv_key,
);
let decoded = read_pdf_to_scene_with_certificate(&pdf, &cred).expect("decode");
assert_eq!(decoded.metadata.title.as_deref(), Some("WriterS4"));
assert_eq!(decoded.pages.expect("pages").len(), 1);
}
#[test]
fn writer_s5_v4_aes128_round_trip() {
let (priv_key, pub_key) = rsa_keypair();
let issuer = b"O=writer-s5-v4";
let serial = vec![0x20];
let recipient = writer_recipient_for_keypair(issuer, serial.clone(), &pub_key);
let cfg = oxideav_pdf::PubSecEncoderConfig::pkcs7_s5_v4_aes128(vec![recipient]);
let scene = one_page_scene("WriterS5V4-AES128");
let pdf = oxideav_pdf::write_pdf_from_scene_pubsec_encrypted(&scene, &cfg).expect("encode");
let cred = PubSecCredential::from_parsed(
Certificate {
issuer_der: der::write_sequence(issuer),
serial,
spki_pubkey_bits: None,
validity: None,
..Default::default()
},
priv_key,
);
let decoded = read_pdf_to_scene_with_certificate(&pdf, &cred).expect("decode");
assert_eq!(decoded.metadata.title.as_deref(), Some("WriterS5V4-AES128"));
}
#[test]
fn writer_s5_v5_aes256_round_trip() {
let (priv_key, pub_key) = rsa_keypair();
let issuer = b"O=writer-s5-v5";
let serial = vec![0x42, 0x01, 0x00];
let recipient = writer_recipient_for_keypair(issuer, serial.clone(), &pub_key);
let cfg = oxideav_pdf::PubSecEncoderConfig::pkcs7_s5_v5_aes256(vec![recipient]);
let scene = one_page_scene("WriterS5V5-AES256");
let pdf = oxideav_pdf::write_pdf_from_scene_pubsec_encrypted(&scene, &cfg).expect("encode");
let cred = PubSecCredential::from_parsed(
Certificate {
issuer_der: der::write_sequence(issuer),
serial,
spki_pubkey_bits: None,
validity: None,
..Default::default()
},
priv_key,
);
let decoded = read_pdf_to_scene_with_certificate(&pdf, &cred).expect("decode");
assert_eq!(decoded.metadata.title.as_deref(), Some("WriterS5V5-AES256"));
}
#[test]
fn writer_s5_v5_two_recipients_either_can_open() {
let (priv_a, pub_a) = rsa_keypair();
let (priv_b, pub_b) = rsa_keypair();
let issuer_a = b"O=writer-multi-A";
let issuer_b = b"O=writer-multi-B";
let recipients = vec![
writer_recipient_for_keypair(issuer_a, vec![0xAA], &pub_a),
writer_recipient_for_keypair(issuer_b, vec![0xBB], &pub_b),
];
let cfg = oxideav_pdf::PubSecEncoderConfig::pkcs7_s5_v5_aes256(recipients);
let scene = one_page_scene("MultiRecipient");
let pdf = oxideav_pdf::write_pdf_from_scene_pubsec_encrypted(&scene, &cfg).expect("encode");
let cred_a = PubSecCredential::from_parsed(
Certificate {
issuer_der: der::write_sequence(issuer_a),
serial: vec![0xAA],
spki_pubkey_bits: None,
validity: None,
..Default::default()
},
priv_a,
);
assert_eq!(
read_pdf_to_scene_with_certificate(&pdf, &cred_a)
.expect("A decodes")
.metadata
.title
.as_deref(),
Some("MultiRecipient"),
);
let cred_b = PubSecCredential::from_parsed(
Certificate {
issuer_der: der::write_sequence(issuer_b),
serial: vec![0xBB],
spki_pubkey_bits: None,
validity: None,
..Default::default()
},
priv_b,
);
assert_eq!(
read_pdf_to_scene_with_certificate(&pdf, &cred_b)
.expect("B decodes")
.metadata
.title
.as_deref(),
Some("MultiRecipient"),
);
}
#[test]
fn writer_s5_v5_ski_recipient_form_round_trip() {
let (priv_key, pub_key) = rsa_keypair();
let pubkey_bits = b"OXIDEAV-WRITER-PUBSEC-SKI-32BYTE".to_vec();
use sha1::Digest;
let ski = sha1::Sha1::digest(&pubkey_bits).to_vec();
let recipient = oxideav_pdf::PubSecRecipient::from_subject_key_identifier(ski, pub_key);
let cfg = oxideav_pdf::PubSecEncoderConfig::pkcs7_s5_v5_aes256(vec![recipient]);
let scene = one_page_scene("SKI-form");
let pdf = oxideav_pdf::write_pdf_from_scene_pubsec_encrypted(&scene, &cfg).expect("encode");
let cred = PubSecCredential::from_parsed(
Certificate {
issuer_der: vec![],
serial: vec![],
spki_pubkey_bits: Some(pubkey_bits),
validity: None,
..Default::default()
},
priv_key,
);
let decoded = read_pdf_to_scene_with_certificate(&pdf, &cred).expect("decode");
assert_eq!(decoded.metadata.title.as_deref(), Some("SKI-form"));
}
#[test]
fn open_unencrypted_pdf_via_certificate_works() {
use oxideav_core::vector::{FillRule, Group, Node, Paint, Path, PathNode, Rgba, VectorFrame};
use oxideav_core::TimeBase;
let mut p = Path::new();
p.move_to(oxideav_core::vector::Point::new(10.0, 10.0))
.line_to(oxideav_core::vector::Point::new(110.0, 10.0))
.line_to(oxideav_core::vector::Point::new(110.0, 60.0))
.line_to(oxideav_core::vector::Point::new(10.0, 60.0))
.close();
let frame = VectorFrame {
width: 200.0,
height: 100.0,
view_box: None,
root: Group {
children: vec![Node::Path(PathNode {
path: p,
fill: Some(Paint::Solid(Rgba::opaque(0xFF, 0x80, 0x00))),
stroke: None,
fill_rule: FillRule::NonZero,
})],
..Group::default()
},
pts: None,
time_base: TimeBase::new(1, 1),
};
let pdf = oxideav_pdf::write_pdf(&frame).expect("write");
let (priv_key, _pub) = rsa_keypair();
let cred = PubSecCredential::from_parsed(
Certificate {
issuer_der: der::write_sequence(b"O=anything"),
serial: vec![0x01],
spki_pubkey_bits: None,
validity: None,
..Default::default()
},
priv_key,
);
let scene = read_pdf_to_scene_with_certificate(&pdf, &cred).expect("plain PDF");
assert!(!scene.pages.unwrap().is_empty());
}