use crate::decrypt::{CryptMethod, StandardHandler};
use crate::error::PdfError;
use crate::objects::{Dict, Object};
use super::cms_build::{
build_envelope_aes128, build_envelope_aes256, build_envelope_rc4, rsa_pkcs1_encrypt,
RecipientIdRef, RecipientPlain,
};
use super::PubSecSubFilter;
#[derive(Debug, Clone)]
pub struct PubSecRecipient {
pub rid: RecipientIdRef,
pub public_key: rsa::RsaPublicKey,
}
impl PubSecRecipient {
pub fn from_issuer_and_serial(
issuer_der: Vec<u8>,
serial: Vec<u8>,
public_key: rsa::RsaPublicKey,
) -> Self {
Self {
rid: RecipientIdRef::IssuerAndSerial { issuer_der, serial },
public_key,
}
}
pub fn from_subject_key_identifier(ski: Vec<u8>, public_key: rsa::RsaPublicKey) -> Self {
Self {
rid: RecipientIdRef::SubjectKeyIdentifier { ski },
public_key,
}
}
pub fn from_certificate(
cert: &super::x509::Certificate,
public_key: rsa::RsaPublicKey,
) -> Self {
Self::from_issuer_and_serial(cert.issuer_der.clone(), cert.serial.clone(), public_key)
}
}
#[derive(Debug, Clone)]
pub struct PubSecEncoderConfig {
pub sub_filter: PubSecSubFilter,
pub p: i32,
pub encrypt_metadata: bool,
pub recipients: Vec<PubSecRecipient>,
pub seed: [u8; 20],
pub cek: Vec<u8>,
pub envelope_iv: [u8; 16],
pub aes_iv: [u8; 16],
}
impl PubSecEncoderConfig {
pub fn pkcs7_s4(recipients: Vec<PubSecRecipient>) -> Self {
Self {
sub_filter: PubSecSubFilter::Pkcs7S4,
p: -4,
encrypt_metadata: true,
recipients,
seed: [0x33; 20],
cek: vec![0xA1u8; 16],
envelope_iv: [0; 16],
aes_iv: [0; 16],
}
}
pub fn pkcs7_s5_v4_aes128(recipients: Vec<PubSecRecipient>) -> Self {
Self {
sub_filter: PubSecSubFilter::Pkcs7S5V4 { aes: true },
p: -4,
encrypt_metadata: true,
recipients,
seed: [0x44; 20],
cek: vec![0xB2u8; 16],
envelope_iv: [0x77; 16],
aes_iv: [0; 16],
}
}
pub fn pkcs7_s5_v5_aes256(recipients: Vec<PubSecRecipient>) -> Self {
Self {
sub_filter: PubSecSubFilter::Pkcs7S5V5,
p: -4,
encrypt_metadata: true,
recipients,
seed: [0x55; 20],
cek: vec![0xC3u8; 32],
envelope_iv: [0x77; 16],
aes_iv: [0; 16],
}
}
}
#[derive(Debug, Clone)]
pub struct PubSecEncryptionState {
pub handler: StandardHandler,
pub encrypt_dict: Dict,
pub aes_iv: [u8; 16],
pub file_id: Vec<u8>,
}
impl PubSecEncryptionState {
pub fn into_encryption_state(self) -> crate::encrypt::EncryptionState {
crate::encrypt::EncryptionState {
handler: self.handler,
encrypt_dict: self.encrypt_dict,
file_id: self.file_id,
aes_iv: self.aes_iv,
}
}
pub fn build(config: &PubSecEncoderConfig) -> Result<Self, PdfError> {
if config.recipients.is_empty() {
return Err(PdfError::other(
"PDF pubsec encode: at least one recipient is required",
));
}
let key_length_bits = key_length_bits(config.sub_filter);
let n = key_length_bits / 8;
if config.cek.len() != n {
return Err(PdfError::other(format!(
"PDF pubsec encode: CEK must be {} bytes for SubFilter {:?} (got {})",
n,
config.sub_filter,
config.cek.len()
)));
}
let mut plaintext = Vec::with_capacity(24);
plaintext.extend_from_slice(&config.seed);
let p_bytes = match config.sub_filter {
PubSecSubFilter::Pkcs7S5V5 => (config.p as u32).to_be_bytes(),
_ => (config.p as u32).to_le_bytes(),
};
plaintext.extend_from_slice(&p_bytes);
let mut slots: Vec<RecipientPlain> = Vec::with_capacity(config.recipients.len());
for r in &config.recipients {
let encrypted_key = rsa_pkcs1_encrypt(&r.public_key, &config.cek)?;
slots.push(RecipientPlain {
rid: r.rid.clone(),
encrypted_key,
});
}
let envelope_der =
match config.sub_filter {
PubSecSubFilter::Pkcs7S3 | PubSecSubFilter::Pkcs7S4 => {
build_envelope_rc4(&slots, &plaintext, &config.cek)
}
PubSecSubFilter::Pkcs7S5V4 { aes: false } => {
build_envelope_rc4(&slots, &plaintext, &config.cek)
}
PubSecSubFilter::Pkcs7S5V4 { aes: true } => {
let cek16: [u8; 16] =
config.cek.as_slice().try_into().map_err(|_| {
PdfError::other("PDF pubsec encode: AES-128 CEK length")
})?;
build_envelope_aes128(&slots, &plaintext, &cek16, &config.envelope_iv)
}
PubSecSubFilter::Pkcs7S5V5 => {
let cek32: [u8; 32] =
config.cek.as_slice().try_into().map_err(|_| {
PdfError::other("PDF pubsec encode: AES-256 CEK length")
})?;
build_envelope_aes256(&slots, &plaintext, &cek32, &config.envelope_iv)
}
};
let recipients_blobs = vec![envelope_der.clone()];
let file_key = derive_file_key(
config.sub_filter,
&config.seed,
&recipients_blobs,
config.encrypt_metadata,
key_length_bits,
);
let (method, revision) = match config.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),
};
let handler = StandardHandler {
key: file_key,
method,
revision,
};
let encrypt_dict = build_encrypt_dict(config, &envelope_der, key_length_bits)?;
Ok(Self {
handler,
encrypt_dict,
aes_iv: config.aes_iv,
file_id: b"OXIDEAV-PUBSEC-ID-0123456789ABCD".to_vec(),
})
}
pub fn with_file_id(mut self, file_id: Vec<u8>) -> Self {
self.file_id = file_id;
self
}
}
#[derive(Debug, Clone)]
pub struct PubSecCfGroup {
pub name: String,
pub p: i32,
pub recipients: Vec<PubSecRecipient>,
pub seed: [u8; 20],
pub cek: Vec<u8>,
pub envelope_iv: [u8; 16],
}
impl PubSecCfGroup {
pub fn full_access_aes256(name: impl Into<String>, recipients: Vec<PubSecRecipient>) -> Self {
Self {
name: name.into(),
p: -4,
recipients,
seed: [0xA1; 20],
cek: vec![0xCAu8; 32],
envelope_iv: [0x77; 16],
}
}
pub fn read_only_aes256(name: impl Into<String>, recipients: Vec<PubSecRecipient>) -> Self {
Self {
name: name.into(),
p: i32::from_be_bytes([0xFF, 0xFF, 0xF0, 0xBF]),
recipients,
seed: [0xB2; 20],
cek: vec![0xDBu8; 32],
envelope_iv: [0x88; 16],
}
}
}
#[derive(Debug, Clone)]
pub struct PubSecMultiCfConfig {
pub sub_filter: PubSecSubFilter,
pub encrypt_metadata: bool,
pub groups: Vec<PubSecCfGroup>,
pub aes_iv: [u8; 16],
pub shared_cek: Vec<u8>,
pub shared_seed: [u8; 20],
}
impl PubSecMultiCfConfig {
pub fn build(self) -> Result<PubSecEncryptionState, PdfError> {
if self.groups.is_empty() {
return Err(PdfError::other(
"PDF pubsec multi-CF: at least one group required",
));
}
if !matches!(
self.sub_filter,
PubSecSubFilter::Pkcs7S5V4 { .. } | PubSecSubFilter::Pkcs7S5V5
) {
return Err(PdfError::other(
"PDF pubsec multi-CF: only s5 SubFilters support per-CF recipients",
));
}
let key_length_bits = key_length_bits(self.sub_filter);
let n = key_length_bits / 8;
if self.shared_cek.len() != n {
return Err(PdfError::other(format!(
"PDF pubsec multi-CF: shared CEK must be {} bytes (got {})",
n,
self.shared_cek.len()
)));
}
for g in &self.groups {
if g.recipients.is_empty() {
return Err(PdfError::other(format!(
"PDF pubsec multi-CF: group {} has no recipients",
g.name
)));
}
}
let mut group_envelopes: Vec<Vec<u8>> = Vec::with_capacity(self.groups.len());
for g in &self.groups {
let mut plaintext = Vec::with_capacity(24);
plaintext.extend_from_slice(&self.shared_seed);
let p_bytes = match self.sub_filter {
PubSecSubFilter::Pkcs7S5V5 => (g.p as u32).to_be_bytes(),
_ => (g.p as u32).to_le_bytes(),
};
plaintext.extend_from_slice(&p_bytes);
let mut slots: Vec<RecipientPlain> = Vec::with_capacity(g.recipients.len());
for r in &g.recipients {
let encrypted_key = rsa_pkcs1_encrypt(&r.public_key, &self.shared_cek)?;
slots.push(RecipientPlain {
rid: r.rid.clone(),
encrypted_key,
});
}
let envelope = match self.sub_filter {
PubSecSubFilter::Pkcs7S5V4 { aes: false } => {
super::cms_build::build_envelope_rc4(&slots, &plaintext, &self.shared_cek)
}
PubSecSubFilter::Pkcs7S5V4 { aes: true } => {
let cek16: [u8; 16] =
self.shared_cek.as_slice().try_into().map_err(|_| {
PdfError::other("PDF pubsec multi-CF: AES-128 CEK length")
})?;
super::cms_build::build_envelope_aes128(
&slots,
&plaintext,
&cek16,
&g.envelope_iv,
)
}
PubSecSubFilter::Pkcs7S5V5 => {
let cek32: [u8; 32] =
self.shared_cek.as_slice().try_into().map_err(|_| {
PdfError::other("PDF pubsec multi-CF: AES-256 CEK length")
})?;
super::cms_build::build_envelope_aes256(
&slots,
&plaintext,
&cek32,
&g.envelope_iv,
)
}
_ => unreachable!(),
};
group_envelopes.push(envelope);
}
let file_key = derive_file_key(
self.sub_filter,
&self.shared_seed,
&group_envelopes,
self.encrypt_metadata,
key_length_bits,
);
let (method, revision) = match self.sub_filter {
PubSecSubFilter::Pkcs7S5V4 { aes } => (
if aes {
CryptMethod::Aes128
} else {
CryptMethod::Rc4
},
4u8,
),
PubSecSubFilter::Pkcs7S5V5 => (CryptMethod::Aes256, 6),
_ => unreachable!(),
};
let handler = StandardHandler {
key: file_key,
method,
revision,
};
let encrypt_dict = build_multi_cf_encrypt_dict(
self.sub_filter,
self.encrypt_metadata,
&self.groups,
&group_envelopes,
key_length_bits,
)?;
Ok(PubSecEncryptionState {
handler,
encrypt_dict,
aes_iv: self.aes_iv,
file_id: b"OXIDEAV-PUBSEC-MULTICF-ID-12345!".to_vec(),
})
}
}
fn build_multi_cf_encrypt_dict(
sub_filter: PubSecSubFilter,
encrypt_metadata: bool,
groups: &[PubSecCfGroup],
envelopes: &[Vec<u8>],
key_length_bits: usize,
) -> Result<Dict, PdfError> {
let (sub_filter_name, v, r, cfm) = match sub_filter {
PubSecSubFilter::Pkcs7S5V4 { aes: true } => ("adbe.pkcs7.s5", 4, 4, "AESV2"),
PubSecSubFilter::Pkcs7S5V4 { aes: false } => ("adbe.pkcs7.s5", 4, 4, "V2"),
PubSecSubFilter::Pkcs7S5V5 => ("adbe.pkcs7.s5", 5, 6, "AESV3"),
_ => {
return Err(PdfError::other(
"PDF pubsec multi-CF: only s5 SubFilters supported",
))
}
};
let cf_length_bytes = (key_length_bits / 8) as i64;
let full_recipients = Object::Array(
envelopes
.iter()
.map(|e| Object::LiteralString(e.clone()))
.collect(),
);
let mut cf = Dict::new();
for g in groups {
let inner = Dict::new()
.with("Type", Object::Name("CryptFilter".into()))
.with("CFM", Object::Name(cfm.into()))
.with("Length", Object::Integer(cf_length_bytes))
.with("Recipients", full_recipients.clone());
cf.set(&g.name, Object::Dict(inner));
}
let stmf_name = groups[0].name.clone();
let mut dict = Dict::new()
.with("Filter", Object::Name("Adobe.PPKLite".into()))
.with("SubFilter", Object::Name(sub_filter_name.into()))
.with("V", Object::Integer(v))
.with("R", Object::Integer(r))
.with("Length", Object::Integer(key_length_bits as i64))
.with("P", Object::Integer(groups[0].p as i64))
.with("CF", Object::Dict(cf))
.with("StmF", Object::Name(stmf_name.clone()))
.with("StrF", Object::Name(stmf_name));
if !encrypt_metadata {
dict.set("EncryptMetadata", Object::Bool(false));
}
Ok(dict)
}
fn key_length_bits(sub: PubSecSubFilter) -> usize {
match sub {
PubSecSubFilter::Pkcs7S3 => 40,
PubSecSubFilter::Pkcs7S4 => 128,
PubSecSubFilter::Pkcs7S5V4 { .. } => 128,
PubSecSubFilter::Pkcs7S5V5 => 256,
}
}
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);
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()
}
fn build_encrypt_dict(
config: &PubSecEncoderConfig,
envelope_der: &[u8],
key_length_bits: usize,
) -> Result<Dict, PdfError> {
let (sub_filter_name, v, r) = match config.sub_filter {
PubSecSubFilter::Pkcs7S3 => ("adbe.pkcs7.s3", 1, 2),
PubSecSubFilter::Pkcs7S4 => ("adbe.pkcs7.s4", 2, 3),
PubSecSubFilter::Pkcs7S5V4 { .. } => ("adbe.pkcs7.s5", 4, 4),
PubSecSubFilter::Pkcs7S5V5 => ("adbe.pkcs7.s5", 5, 6),
};
let mut dict = Dict::new()
.with("Filter", Object::Name("Adobe.PPKLite".into()))
.with("SubFilter", Object::Name(sub_filter_name.into()))
.with("V", Object::Integer(v))
.with("R", Object::Integer(r))
.with("Length", Object::Integer(key_length_bits as i64))
.with("P", Object::Integer(config.p as i64));
if !config.encrypt_metadata {
dict.set("EncryptMetadata", Object::Bool(false));
}
let recipients_arr = Object::Array(vec![Object::LiteralString(envelope_der.to_vec())]);
match config.sub_filter {
PubSecSubFilter::Pkcs7S3 | PubSecSubFilter::Pkcs7S4 => {
dict.set("Recipients", recipients_arr);
}
PubSecSubFilter::Pkcs7S5V4 { aes } => {
let cfm = if aes { "AESV2" } else { "V2" };
let std_cf = Dict::new()
.with("Type", Object::Name("CryptFilter".into()))
.with("CFM", Object::Name(cfm.into()))
.with("Length", Object::Integer(16))
.with("Recipients", recipients_arr.clone());
let cf = Dict::new().with("DefaultCryptFilter", Object::Dict(std_cf));
dict.set("CF", Object::Dict(cf));
dict.set("StmF", Object::Name("DefaultCryptFilter".into()));
dict.set("StrF", Object::Name("DefaultCryptFilter".into()));
dict.set("Recipients", recipients_arr);
}
PubSecSubFilter::Pkcs7S5V5 => {
let std_cf = Dict::new()
.with("Type", Object::Name("CryptFilter".into()))
.with("CFM", Object::Name("AESV3".into()))
.with("Length", Object::Integer(32))
.with("Recipients", recipients_arr.clone());
let cf = Dict::new().with("DefaultCryptFilter", Object::Dict(std_cf));
dict.set("CF", Object::Dict(cf));
dict.set("StmF", Object::Name("DefaultCryptFilter".into()));
dict.set("StrF", Object::Name("DefaultCryptFilter".into()));
dict.set("Recipients", recipients_arr);
}
}
Ok(dict)
}
#[derive(Debug, Clone)]
pub struct KariRecipient {
pub issuer_der: Vec<u8>,
pub serial: Vec<u8>,
pub curve: super::kari::KariCurve,
pub kdf: super::kari::KariKdf,
pub recipient_pub_bytes: Vec<u8>,
pub ephemeral_scalar: Vec<u8>,
}
impl KariRecipient {
pub fn p256(
issuer_der: Vec<u8>,
serial: Vec<u8>,
recipient_pub_sec1: Vec<u8>,
ephemeral_scalar: Vec<u8>,
) -> Self {
Self {
issuer_der,
serial,
curve: super::kari::KariCurve::P256,
kdf: super::kari::KariKdf::X963Sha256,
recipient_pub_bytes: recipient_pub_sec1,
ephemeral_scalar,
}
}
pub fn p384(
issuer_der: Vec<u8>,
serial: Vec<u8>,
recipient_pub_sec1: Vec<u8>,
ephemeral_scalar: Vec<u8>,
) -> Self {
Self {
issuer_der,
serial,
curve: super::kari::KariCurve::P384,
kdf: super::kari::KariKdf::X963Sha384,
recipient_pub_bytes: recipient_pub_sec1,
ephemeral_scalar,
}
}
pub fn p521(
issuer_der: Vec<u8>,
serial: Vec<u8>,
recipient_pub_sec1: Vec<u8>,
ephemeral_scalar: Vec<u8>,
) -> Self {
Self {
issuer_der,
serial,
curve: super::kari::KariCurve::P521,
kdf: super::kari::KariKdf::X963Sha512,
recipient_pub_bytes: recipient_pub_sec1,
ephemeral_scalar,
}
}
pub fn x25519(
issuer_der: Vec<u8>,
serial: Vec<u8>,
recipient_pub_x25519: Vec<u8>,
ephemeral_scalar: Vec<u8>,
) -> Self {
Self {
issuer_der,
serial,
curve: super::kari::KariCurve::X25519,
kdf: super::kari::KariKdf::X963Sha256,
recipient_pub_bytes: recipient_pub_x25519,
ephemeral_scalar,
}
}
pub fn x25519_hkdf_sha256(
issuer_der: Vec<u8>,
serial: Vec<u8>,
recipient_pub_x25519: Vec<u8>,
ephemeral_scalar: Vec<u8>,
) -> Self {
Self {
issuer_der,
serial,
curve: super::kari::KariCurve::X25519,
kdf: super::kari::KariKdf::HkdfSha256,
recipient_pub_bytes: recipient_pub_x25519,
ephemeral_scalar,
}
}
pub fn x25519_hkdf_sha384(
issuer_der: Vec<u8>,
serial: Vec<u8>,
recipient_pub_x25519: Vec<u8>,
ephemeral_scalar: Vec<u8>,
) -> Self {
Self {
issuer_der,
serial,
curve: super::kari::KariCurve::X25519,
kdf: super::kari::KariKdf::HkdfSha384,
recipient_pub_bytes: recipient_pub_x25519,
ephemeral_scalar,
}
}
pub fn x25519_hkdf_sha512(
issuer_der: Vec<u8>,
serial: Vec<u8>,
recipient_pub_x25519: Vec<u8>,
ephemeral_scalar: Vec<u8>,
) -> Self {
Self {
issuer_der,
serial,
curve: super::kari::KariCurve::X25519,
kdf: super::kari::KariKdf::HkdfSha512,
recipient_pub_bytes: recipient_pub_x25519,
ephemeral_scalar,
}
}
}
#[derive(Debug, Clone)]
pub struct PubSecKariConfig {
pub p: i32,
pub encrypt_metadata: bool,
pub recipients: Vec<KariRecipient>,
pub ukm: Option<Vec<u8>>,
pub seed: [u8; 20],
pub cek: [u8; 32],
pub envelope_iv: [u8; 16],
pub aes_iv: [u8; 16],
}
impl PubSecKariConfig {
pub fn aes256(recipients: Vec<KariRecipient>) -> Self {
Self {
p: -4,
encrypt_metadata: true,
recipients,
ukm: None,
seed: [0x6Au8; 20],
cek: [0x9Cu8; 32],
envelope_iv: [0x77; 16],
aes_iv: [0; 16],
}
}
}
impl PubSecEncryptionState {
pub fn build_kari(config: &PubSecKariConfig) -> Result<Self, PdfError> {
if config.recipients.is_empty() {
return Err(PdfError::other(
"PDF pubsec KARI encode: at least one recipient is required",
));
}
let mut plaintext = Vec::with_capacity(24);
plaintext.extend_from_slice(&config.seed);
plaintext.extend_from_slice(&(config.p as u32).to_be_bytes());
let wrap = super::kari::WrapAlgorithm::Aes256;
let mut karis: Vec<Vec<u8>> = Vec::with_capacity(config.recipients.len());
for r in &config.recipients {
if r.recipient_pub_bytes.len() != r.curve.pub_point_len() {
return Err(PdfError::other(format!(
"PDF pubsec KARI encode: recipient pub_bytes {} != expected {} for {:?}",
r.recipient_pub_bytes.len(),
r.curve.pub_point_len(),
r.curve
)));
}
let (originator_pub, wrapped) = super::kari::wrap_cek_for_recipient_with_kdf(
r.curve,
r.kdf,
&r.ephemeral_scalar,
&r.recipient_pub_bytes,
config.ukm.as_deref(),
&config.cek,
wrap,
)?;
let kea_params = super::der::write_sequence(&super::der::write_oid(wrap.oid()));
let originator = super::cms_build::OriginatorIdRef::OriginatorKey {
algorithm_oid: r.curve.algorithm_oid().to_vec(),
algorithm_params: r.curve.algorithm_params(),
public_key: originator_pub,
};
let recipient_slot = super::cms_build::KariRecipientPlain {
rid: super::cms_build::KariRecipientIdRef::IssuerAndSerial {
issuer_der: r.issuer_der.clone(),
serial: r.serial.clone(),
},
encrypted_key: wrapped,
};
let envelope = super::cms_build::build_envelope_kari_aes256(
&originator,
config.ukm.as_deref(),
r.kdf.kea_oid(),
&kea_params,
&[recipient_slot],
&plaintext,
&config.cek,
&config.envelope_iv,
);
karis.push(envelope);
}
let key_length_bits = 256usize;
let file_key = derive_file_key(
PubSecSubFilter::Pkcs7S5V5,
&config.seed,
&karis,
config.encrypt_metadata,
key_length_bits,
);
let handler = StandardHandler {
key: file_key,
method: CryptMethod::Aes256,
revision: 6,
};
let recipients_arr = Object::Array(
karis
.iter()
.map(|e| Object::LiteralString(e.clone()))
.collect(),
);
let std_cf = Dict::new()
.with("Type", Object::Name("CryptFilter".into()))
.with("CFM", Object::Name("AESV3".into()))
.with("Length", Object::Integer(32))
.with("Recipients", recipients_arr.clone());
let cf = Dict::new().with("DefaultCryptFilter", Object::Dict(std_cf));
let mut dict = Dict::new()
.with("Filter", Object::Name("Adobe.PPKLite".into()))
.with("SubFilter", Object::Name("adbe.pkcs7.s5".into()))
.with("V", Object::Integer(5))
.with("R", Object::Integer(6))
.with("Length", Object::Integer(256))
.with("P", Object::Integer(config.p as i64))
.with("CF", Object::Dict(cf))
.with("StmF", Object::Name("DefaultCryptFilter".into()))
.with("StrF", Object::Name("DefaultCryptFilter".into()))
.with("Recipients", recipients_arr);
if !config.encrypt_metadata {
dict.set("EncryptMetadata", Object::Bool(false));
}
Ok(PubSecEncryptionState {
handler,
encrypt_dict: dict,
aes_iv: config.aes_iv,
file_id: b"OXIDEAV-PUBSEC-KARI-ID-12345!XYZ".to_vec(),
})
}
}
#[cfg(test)]
mod tests {
use super::super::open_with_certificate;
use super::*;
use crate::pubsec::x509::Certificate;
use crate::pubsec::PubSecCredential;
fn keypair() -> (rsa::RsaPrivateKey, rsa::RsaPublicKey) {
let mut rng = rsa::rand_core::OsRng;
let priv_key = rsa::RsaPrivateKey::new(&mut rng, 2048).expect("RSA");
let pub_key = rsa::RsaPublicKey::from(&priv_key);
(priv_key, pub_key)
}
fn fake_cert(issuer_der: Vec<u8>, serial: Vec<u8>) -> Certificate {
Certificate {
issuer_der,
serial,
..Default::default()
}
}
#[test]
fn s4_writer_then_reader_round_trip_via_ias() {
let (priv_key, pub_key) = keypair();
let issuer_der = super::super::der::write_sequence(b"O=enc-test");
let serial = vec![0x10, 0x20];
let cfg = PubSecEncoderConfig::pkcs7_s4(vec![PubSecRecipient::from_issuer_and_serial(
issuer_der.clone(),
serial.clone(),
pub_key,
)]);
let state = PubSecEncryptionState::build(&cfg).expect("build");
let cred = PubSecCredential::from_parsed(fake_cert(issuer_der, serial), priv_key);
let handler = open_with_certificate(&state.encrypt_dict, &cred)
.expect("open ok")
.expect("matched");
assert_eq!(handler.method, state.handler.method);
assert_eq!(handler.revision, state.handler.revision);
assert_eq!(handler.key, state.handler.key);
}
#[test]
fn s5_v4_aes128_writer_then_reader_round_trip() {
let (priv_key, pub_key) = keypair();
let issuer_der = super::super::der::write_sequence(b"O=enc-aes-128");
let serial = vec![0x05];
let cfg =
PubSecEncoderConfig::pkcs7_s5_v4_aes128(vec![PubSecRecipient::from_issuer_and_serial(
issuer_der.clone(),
serial.clone(),
pub_key,
)]);
let state = PubSecEncryptionState::build(&cfg).expect("build");
let cred = PubSecCredential::from_parsed(fake_cert(issuer_der, serial), priv_key);
let handler = open_with_certificate(&state.encrypt_dict, &cred)
.expect("open")
.expect("matched");
assert_eq!(handler.method, CryptMethod::Aes128);
assert_eq!(handler.revision, 4);
}
#[test]
fn s5_v5_aes256_writer_then_reader_round_trip() {
let (priv_key, pub_key) = keypair();
let issuer_der = super::super::der::write_sequence(b"O=enc-aes-256");
let serial = vec![0x42, 0x01];
let cfg =
PubSecEncoderConfig::pkcs7_s5_v5_aes256(vec![PubSecRecipient::from_issuer_and_serial(
issuer_der.clone(),
serial.clone(),
pub_key,
)]);
let state = PubSecEncryptionState::build(&cfg).expect("build");
let cred = PubSecCredential::from_parsed(fake_cert(issuer_der, serial), priv_key);
let handler = open_with_certificate(&state.encrypt_dict, &cred)
.expect("open")
.expect("matched");
assert_eq!(handler.method, CryptMethod::Aes256);
assert_eq!(handler.revision, 6);
assert_eq!(handler.key.len(), 32);
}
#[test]
fn s5_v5_writer_via_ski_recipient_form() {
let (priv_key, pub_key) = keypair();
let pubkey_bits = b"OXIDEAV-PUBSEC-WRITER-SKI-MATCH!".to_vec();
use sha1::Digest;
let ski = sha1::Sha1::digest(&pubkey_bits).to_vec();
let mut cfg = PubSecEncoderConfig::pkcs7_s5_v5_aes256(vec![
PubSecRecipient::from_subject_key_identifier(ski.clone(), pub_key),
]);
cfg.seed = [0xAB; 20];
let state = PubSecEncryptionState::build(&cfg).expect("build");
let cred = PubSecCredential::from_parsed(
Certificate {
spki_pubkey_bits: Some(pubkey_bits),
..Default::default()
},
priv_key,
);
let handler = open_with_certificate(&state.encrypt_dict, &cred)
.expect("open")
.expect("SKI matched");
assert_eq!(handler.method, CryptMethod::Aes256);
assert_eq!(handler.key.len(), 32);
}
#[test]
fn empty_recipients_rejected() {
let cfg = PubSecEncoderConfig::pkcs7_s5_v5_aes256(vec![]);
let err = PubSecEncryptionState::build(&cfg).unwrap_err();
assert!(format!("{err}").contains("at least one recipient"));
}
}