oxideav_pdf/pubsec/cms.rs
1//! Minimal CMS (Cryptographic Message Syntax, RFC 5652) parser for
2//! the `EnvelopedData` content type used by the PDF public-key
3//! security handler (ISO 32000-1 §7.6.4 — see `docs/document/pdf/PDF32000_2008.pdf`).
4//!
5//! Only the subset PDF readers actually need is implemented:
6//!
7//! * `ContentInfo` whose `contentType` is the OID
8//! `1.2.840.113549.1.7.3` (`id-envelopedData`).
9//! * `EnvelopedData` versions 0, 2, and 3.
10//! * `RecipientInfo` of variant `KeyTransRecipientInfo` (RFC 5652
11//! §6.2.1) and `KeyAgreeRecipientInfo` (§6.2.2 — round 12 decoder
12//! side: ECDH / DH / static-static recipients; the originator
13//! public key + UKM are surfaced; the wrapped CEK lands as a
14//! `RecipientEncryptedKey` slot inside the KARI).
15//! * `RecipientIdentifier` of variant `IssuerAndSerialNumber` (CMS
16//! v0) or `[0] SubjectKeyIdentifier` (CMS v2). Round 11 wires the
17//! SKI variant through to the matcher (the pubsec module computes
18//! the SHA-1 of the user cert's `SubjectPublicKeyInfo` BIT STRING
19//! contents per RFC 5280 §4.2.1.2 and compares it to the recipient
20//! slot's SKI octet string).
21//! * `EncryptedContentInfo` whose `contentType` is `id-data` and
22//! whose `contentEncryptionAlgorithm` is one of the algorithm
23//! identifiers the PDF spec allows (RC4 / AES-128-CBC /
24//! AES-256-CBC).
25//!
26//! Provenance: RFC 5652 §6 only. Encoding test fixtures consume
27//! the symmetric writer side which lives in `cms_build.rs`.
28
29use crate::error::PdfError;
30
31use super::der::{
32 maybe_read_context, read_context, read_expected, read_integer_bytes, read_integer_u64,
33 read_octet_string, read_oid, read_sequence, read_set, Class,
34};
35
36/// OID 1.2.840.113549.1.7.3 — id-envelopedData.
37pub const OID_ENVELOPED_DATA: [u64; 7] = [1, 2, 840, 113549, 1, 7, 3];
38
39/// OID 1.2.840.113549.1.7.2 — id-signedData (RFC 5652 §5.1). Round-19
40/// adds parser-side recognition; verification is deferred (the round
41/// surfaces `SignedData` structurally so callers can route on the
42/// signed contents + per-signer attributes without a built-in verify
43/// dispatch).
44pub const OID_SIGNED_DATA: [u64; 7] = [1, 2, 840, 113549, 1, 7, 2];
45
46/// OID 1.2.840.113549.1.7.1 — id-data (the contentType inside
47/// `EncryptedContentInfo`).
48pub const OID_DATA: [u64; 7] = [1, 2, 840, 113549, 1, 7, 1];
49
50/// OID 1.2.840.113549.1.1.1 — rsaEncryption (RSAES-PKCS1-v1_5 in CMS).
51pub const OID_RSA_ENCRYPTION: [u64; 7] = [1, 2, 840, 113549, 1, 1, 1];
52
53/// OID 1.2.840.113549.3.4 — RC4. (PDF s3/s4 with `CFM=V2`).
54pub const OID_RC4: [u64; 6] = [1, 2, 840, 113549, 3, 4];
55
56/// OID 2.16.840.1.101.3.4.1.2 — id-aes128-CBC.
57pub const OID_AES128_CBC: [u64; 9] = [2, 16, 840, 1, 101, 3, 4, 1, 2];
58
59/// OID 2.16.840.1.101.3.4.1.42 — id-aes256-CBC.
60pub const OID_AES256_CBC: [u64; 9] = [2, 16, 840, 1, 101, 3, 4, 1, 42];
61
62/// OID 1.2.840.113549.3.2 — `rc2-cbc` (RFC 2268 + RFC 3217 §3). Used
63/// by legacy CMS envelopes whose `EncryptedContentInfo.contentEncryptionAlgorithm`
64/// names RC2-CBC. Round-17 adds read-only support so legacy
65/// PDF archives still open; PDF 2.0 deprecates RC2 entirely.
66///
67/// Parameters per RFC 3370 §5.1: SEQUENCE { rc2ParameterVersion INTEGER
68/// OPTIONAL DEFAULT 32, iv OCTET STRING (size 8) }. The
69/// `rc2ParameterVersion` ↔ effective-key-bits mapping (RFC 2268 §6) is:
70/// version 160 → 40 bits, 120 → 64 bits, 58 → 128 bits. We only
71/// support the 128-bit effective-key variant on decode (the 40 / 64-bit
72/// variants are export-grade legacy and would also be acceptable in
73/// principle, but the surface we surface today follows the most-common
74/// modern usage).
75pub const OID_RC2_CBC: [u64; 6] = [1, 2, 840, 113549, 3, 2];
76
77/// OID 1.2.840.113549.3.7 — `des-EDE3-CBC` (RFC 3370 §5.2 + RFC 5652
78/// §12.4). Used by legacy CMS envelopes whose
79/// `EncryptedContentInfo.contentEncryptionAlgorithm` names triple-DES
80/// in CBC mode. Round-17 adds read-only support; PDF 2.0 deprecates 3DES.
81///
82/// Parameters: OCTET STRING (size 8) — the 8-byte CBC IV.
83pub const OID_DES_EDE3_CBC: [u64; 6] = [1, 2, 840, 113549, 3, 7];
84
85/// Symmetric content-encryption algorithm extracted from the
86/// `EnvelopedData::encryptedContentInfo::contentEncryptionAlgorithm`
87/// field. Only the algorithms actually referenced by ISO 32000-1
88/// §7.6.4.3 + the read-only legacy round-17 set are listed.
89#[derive(Debug, Clone)]
90pub enum ContentEncryption {
91 /// RC4 stream cipher; the algorithm identifier carries no
92 /// parameters beyond the OID itself.
93 Rc4,
94 /// AES-128-CBC. The IV is the 16-byte OCTET STRING parameters
95 /// payload.
96 Aes128Cbc { iv: [u8; 16] },
97 /// AES-256-CBC. Same parameter shape as AES-128.
98 Aes256Cbc { iv: [u8; 16] },
99 /// **Round-17 read-only** — RC2-CBC (RFC 2268 + RFC 3217 §3). The
100 /// 8-byte CBC IV is carried in the parameters SEQUENCE alongside
101 /// the optional `rc2ParameterVersion` (which we surface as the
102 /// effective-key bit length per RFC 2268 §6 — 40 / 64 / 128).
103 /// PDF 2.0 deprecates RC2; we accept it on decode only.
104 Rc2Cbc {
105 /// RC2 effective-key bit length (RFC 2268 §6) — `40`, `64`, or
106 /// `128`. Defaults to `32` per RFC 3370 §5.1's
107 /// `rc2ParameterVersion` DEFAULT (which translates to 32 effective
108 /// bits — but RFC 3370 also explicitly lists 32 → 32 and writers
109 /// commonly omit the field entirely, in which case we fall back
110 /// to 32).
111 effective_key_bits: u32,
112 /// 8-byte CBC IV.
113 iv: [u8; 8],
114 },
115 /// **Round-17 read-only** — DES-EDE3-CBC (3DES, RFC 3370 §5.2). The
116 /// 8-byte CBC IV is the parameters payload. PDF 2.0 deprecates 3DES;
117 /// we accept it on decode only.
118 DesEde3Cbc {
119 /// 8-byte CBC IV.
120 iv: [u8; 8],
121 },
122}
123
124/// Identifier-and-serial-number pair (RFC 5280 §A.1) that points at
125/// one of the recipient's certificates. Bytes are the raw DER of the
126/// `Name` for `issuer` (so the matcher can byte-compare against the
127/// recipient's own certificate's `issuer` directly), and the raw
128/// big-endian two's-complement INTEGER body for `serial_number`.
129#[derive(Debug, Clone)]
130pub struct IssuerAndSerial {
131 /// DER-encoded `issuer` Name (a SEQUENCE OF RelativeDistinguishedName).
132 pub issuer_der: Vec<u8>,
133 /// Raw INTEGER body bytes of `serialNumber`. RFC 5280 §4.1.2.2
134 /// allows up to 20 octets; we keep the full big-endian body so a
135 /// byte-for-byte match against the user's cert serial is exact.
136 pub serial: Vec<u8>,
137}
138
139/// CMS `RecipientIdentifier` (RFC 5652 §6.2.1) — the CHOICE that picks
140/// between `IssuerAndSerialNumber` (CMS v0) and `SubjectKeyIdentifier`
141/// (CMS v2). The SKI form carries the bare 20-byte SHA-1 of the
142/// recipient cert's `SubjectPublicKeyInfo` BIT STRING contents — see
143/// RFC 5280 §4.2.1.2 method 1.
144#[derive(Debug, Clone)]
145pub enum RecipientId {
146 /// `IssuerAndSerialNumber` (CMS v0). The matcher compares this to
147 /// the user cert's `(issuer_der, serial)` pair byte-for-byte.
148 IssuerAndSerial(IssuerAndSerial),
149 /// `[0] SubjectKeyIdentifier` (CMS v2). The matcher compares the
150 /// raw octet-string body to the SHA-1 of the user cert's
151 /// `SubjectPublicKeyInfo` BIT STRING contents.
152 SubjectKeyIdentifier(Vec<u8>),
153}
154
155/// `KeyTransRecipientInfo` (RFC 5652 §6.2.1) — the only RecipientInfo
156/// flavour the public-key handler ever uses in practice. RSAES-PKCS1
157/// v1.5 is the one key-encryption algorithm we accept (per the
158/// pre-AES algorithm list in ISO 32000-1 §7.6.4.3 + the RFC 5652
159/// recommendation).
160#[derive(Debug, Clone)]
161pub struct KeyTransRecipientInfo {
162 /// Recipient identifier — IssuerAndSerial (v0) or SKI (v2).
163 pub rid: RecipientId,
164 /// Algorithm identifier of the key-encryption algorithm. We only
165 /// accept `OID_RSA_ENCRYPTION` here.
166 pub key_encryption_oid: Vec<u64>,
167 /// The encrypted content-encryption key, RSA-PKCS1-v1.5 wrapped
168 /// to the recipient's public RSA key.
169 pub encrypted_key: Vec<u8>,
170}
171
172/// `OriginatorPublicKey` (RFC 5652 §6.2.2) — the originator's
173/// ephemeral or static public key carried as a BIT STRING with an
174/// `AlgorithmIdentifier` describing the curve / group. Used by the
175/// recipient (along with their own private key) to derive the shared
176/// secret that wraps the content-encryption key in a KARI envelope.
177#[derive(Debug, Clone)]
178pub struct OriginatorPublicKey {
179 /// AlgorithmIdentifier OID (e.g. ecPublicKey, dhpublicnumber).
180 pub algorithm_oid: Vec<u64>,
181 /// Raw AlgorithmIdentifier `parameters` field bytes (e.g. an OID
182 /// for the named curve). Empty when the encoded form was a NULL
183 /// or absent.
184 pub algorithm_params: Vec<u8>,
185 /// `subjectPublicKey` BIT STRING contents (no leading unused-bits
186 /// byte). For ECDH this is the encoded EC point.
187 pub public_key: Vec<u8>,
188}
189
190/// `OriginatorIdentifierOrKey` (RFC 5652 §6.2.2) — the CHOICE that
191/// identifies the originator side of a key-agreement recipient. We
192/// surface every arm so callers can route on the originator type
193/// (an importing client may, for example, prefer one originator to
194/// another when multiple KARIs are present).
195#[derive(Debug, Clone)]
196pub enum OriginatorId {
197 /// Originator is identified by their `IssuerAndSerialNumber`.
198 IssuerAndSerial(IssuerAndSerial),
199 /// Originator is identified by their SubjectKeyIdentifier (the
200 /// 20-byte SHA-1 of their cert's SPKI BIT STRING contents).
201 SubjectKeyIdentifier(Vec<u8>),
202 /// Originator's public key is carried in-band (no certificate
203 /// reference required).
204 OriginatorKey(OriginatorPublicKey),
205}
206
207/// `OtherKeyAttribute` (RFC 5652 §10.2.7) — opaque application-defined
208/// recipient-key attribute carried inside a `RecipientKeyIdentifier`.
209///
210/// ```asn.1
211/// OtherKeyAttribute ::= SEQUENCE {
212/// keyAttrId OBJECT IDENTIFIER,
213/// keyAttr ANY DEFINED BY keyAttrId OPTIONAL
214/// }
215/// ```
216///
217/// We surface the raw bytes (both the OID arc list and the unparsed
218/// attribute body) so callers can interpret application-specific
219/// attributes per their own conventions.
220#[derive(Debug, Clone, PartialEq, Eq)]
221pub struct OtherKeyAttribute {
222 /// `keyAttrId` OID arcs.
223 pub key_attr_id: Vec<u64>,
224 /// Raw bytes of the optional `keyAttr` ANY value (DER-encoded — the
225 /// caller is expected to parse it per the OID's contract). Empty
226 /// when absent.
227 pub key_attr: Vec<u8>,
228}
229
230/// `KeyAgreeRecipientIdentifier` (RFC 5652 §6.2.2) — identifies one
231/// recipient inside a KARI's `recipientEncryptedKeys` SEQUENCE. Either
232/// the legacy `IssuerAndSerial` form or a `RecipientKeyIdentifier` —
233/// which carries an SKI plus optional `date` + `other` attributes.
234///
235/// Round 18: the OPTIONAL `date` (`GeneralizedTime`) and `other`
236/// (`OtherKeyAttribute`) fields of `RecipientKeyIdentifier` are now
237/// captured rather than silently discarded. The `date` field lets the
238/// originator pin "this envelope is valid for the recipient cert that
239/// was active at this instant" — useful for long-lived archives where
240/// multiple cert generations exist for the same SKI. See
241/// [`super::TrustStore::find_with_temporal_validity`].
242#[derive(Debug, Clone)]
243pub enum KeyAgreeRecipientId {
244 /// Legacy `IssuerAndSerialNumber` — same shape as KTRI v0.
245 IssuerAndSerial(IssuerAndSerial),
246 /// `[0] IMPLICIT RecipientKeyIdentifier` (RFC 5652 §6.2.2). The
247 /// `ski` is the recipient cert's SubjectKeyIdentifier; the OPTIONAL
248 /// `date` (RFC 5280 §4.1.2.5.2 `GeneralizedTime` — `YYYYMMDDHHMMSSZ`)
249 /// and `other` (`OtherKeyAttribute`) fields are surfaced as raw
250 /// bytes / structured arms when present.
251 RecipientKeyIdentifier {
252 /// 20-byte SHA-1 of the recipient cert's SPKI BIT STRING
253 /// contents (RFC 5280 §4.2.1.2 method 1).
254 ski: Vec<u8>,
255 /// OPTIONAL `date` field — raw `GeneralizedTime` ASCII bytes
256 /// per RFC 5280 §4.1.2.5.2 (e.g. `b"20260510120000Z"`). `None`
257 /// when the field was absent. Round 18.
258 date: Option<Vec<u8>>,
259 /// OPTIONAL `other` field — the parsed `OtherKeyAttribute`
260 /// SEQUENCE. `None` when absent. Round 18.
261 other: Option<OtherKeyAttribute>,
262 },
263}
264
265/// `RecipientEncryptedKey` (RFC 5652 §6.2.2) — one wrapped CEK inside
266/// a KARI envelope. The KARI itself holds a SEQUENCE OF these.
267#[derive(Debug, Clone)]
268pub struct RecipientEncryptedKey {
269 /// Recipient identifier (CHOICE issuerAndSerial / RKID-via-SKI).
270 pub rid: KeyAgreeRecipientId,
271 /// Wrapped content-encryption key. The unwrap algorithm is named
272 /// by the parent KARI's `keyEncryptionAlgorithm` (typically a
273 /// key-wrap algorithm such as `id-aes128-wrap` paired with a
274 /// key-derivation function like `dhSinglePass-stdDH-sha256kdf-scheme`).
275 pub encrypted_key: Vec<u8>,
276}
277
278/// `KeyAgreeRecipientInfo` (RFC 5652 §6.2.2) — the second
279/// `RecipientInfo` CHOICE arm. Used with ECDH / DH-based recipients
280/// (vs RSA-based KTRI). `version` is always 3.
281#[derive(Debug, Clone)]
282pub struct KeyAgreeRecipientInfo {
283 /// Originator side identifier / key.
284 pub originator: OriginatorId,
285 /// Optional UserKeyingMaterial — extra randomness mixed into the
286 /// KDF on both sides. Empty `Vec` means "absent".
287 pub ukm: Vec<u8>,
288 /// `keyEncryptionAlgorithm` OID — names the KDF + key-wrap
289 /// combination (e.g. `dhSinglePass-stdDH-sha256kdf-scheme`).
290 pub key_encryption_oid: Vec<u64>,
291 /// Raw `keyEncryptionAlgorithm` parameters bytes. For
292 /// `dhSinglePass-stdDH-sha*-kdf` this is itself a SEQUENCE
293 /// containing the key-wrap algorithm's OID + parameters.
294 pub key_encryption_params: Vec<u8>,
295 /// One or more recipient slots; each slot's `encryptedKey` is the
296 /// CEK wrapped using the shared secret derived from the originator
297 /// + recipient pair (and the `keyEncryptionAlgorithm`'s KDF).
298 pub recipient_encrypted_keys: Vec<RecipientEncryptedKey>,
299}
300
301/// `RecipientInfo` CHOICE — round 12 surfaces both KTRI (RSA) and
302/// KARI (DH/ECDH) variants. KEKRI (`[2] kekri`), PWRI (`[3] pwri`),
303/// and ORI (`[4] ori`) are still skipped — the PDF spec does not
304/// reference them and adding them would expand the threat surface
305/// without serving a use case.
306#[derive(Debug, Clone)]
307pub enum RecipientInfoVariant {
308 /// `KeyTransRecipientInfo` — RSA-based, the round-10/11 path.
309 KeyTrans(KeyTransRecipientInfo),
310 /// `KeyAgreeRecipientInfo` — DH/ECDH-based, round 12 decoder.
311 KeyAgree(KeyAgreeRecipientInfo),
312}
313
314/// `OriginatorInfo` (RFC 5652 §10.2.1) — the optional `[0] IMPLICIT`
315/// originator-side certificate + revocation chain that an envelope MAY
316/// carry alongside the recipient-info SET.
317///
318/// ```asn.1
319/// OriginatorInfo ::= SEQUENCE {
320/// certs [0] IMPLICIT CertificateSet OPTIONAL,
321/// crls [1] IMPLICIT RevocationInfoChoices OPTIONAL
322/// }
323/// ```
324///
325/// Round 18 surfaces this structurally-parsed-but-previously-discarded
326/// chain so callers (e.g. validation pipelines) can inspect the
327/// originator's transmitted certificate / CRL bundle. Each `certs[]`
328/// element is the raw DER bytes of one CertificateChoices alternative —
329/// typically an X.509 v3 `Certificate` SEQUENCE; we surface every
330/// alternative shape as opaque DER so the caller can dispatch on the
331/// outer tag (`SEQUENCE` for the X.509 v3 / v1 form vs context-specific
332/// `[0]..[3]` for the extended-cert / attribute-cert / other-cert
333/// alternatives of RFC 5652 §10.2.2). Each `crls[]` element is the raw
334/// DER of one RevocationInfoChoices alternative — typically an X.509
335/// `CertificateList` SEQUENCE.
336///
337/// The bytes captured for each entry include the outer tag and length —
338/// they are byte-identical to what would be written back by a
339/// re-encoder, so a caller can re-parse the inner X.509 / CRL via the
340/// crate's [`super::x509::Certificate::parse`] helper without
341/// reconstruction.
342#[derive(Debug, Clone, Default)]
343pub struct OriginatorInfo {
344 /// Raw DER bytes of each entry in the OPTIONAL `certs[0]` set.
345 /// Empty when the field was absent.
346 pub certs: Vec<Vec<u8>>,
347 /// Raw DER bytes of each entry in the OPTIONAL `crls[1]` set.
348 /// Empty when the field was absent.
349 pub crls: Vec<Vec<u8>>,
350}
351
352impl OriginatorInfo {
353 /// `true` when both `certs[]` and `crls[]` are empty (i.e. the
354 /// envelope omitted `OriginatorInfo` or carried it as an empty
355 /// SEQUENCE).
356 pub fn is_empty(&self) -> bool {
357 self.certs.is_empty() && self.crls.is_empty()
358 }
359}
360
361/// Parsed CMS `EnvelopedData` reduced to the fields the PDF public-key
362/// handler consumes. Recipients of unsupported variants (`kekri`,
363/// `pwri`, `ori`) are skipped over silently so a pre-existing PDF
364/// written with mixed recipient types can still be opened by a
365/// supported variant's user.
366#[derive(Debug, Clone)]
367pub struct EnvelopedData {
368 /// Backwards-compatible KTRI-only view of the recipients SET. Code
369 /// written against round 10/11 keeps working — only KTRI slots are
370 /// surfaced through this list. Round 12 introduces [`Self::all`]
371 /// for callers that want the KARI slots too.
372 pub recipients: Vec<KeyTransRecipientInfo>,
373 /// Round-12 view: every recognised RecipientInfo (KTRI + KARI) in
374 /// declaration order. KEKRI / PWRI / ORI are still skipped.
375 pub all_recipients: Vec<RecipientInfoVariant>,
376 /// Symmetric algorithm used to protect the envelope's content.
377 pub content_encryption: ContentEncryption,
378 /// The encrypted enveloped data (the bytes that decrypt to the
379 /// 20-byte seed + 4-byte permissions blob).
380 pub encrypted_content: Vec<u8>,
381 /// Round-18: the OPTIONAL originator-side cert / CRL chain
382 /// (RFC 5652 §10.2.1 `OriginatorInfo`). Empty when the envelope
383 /// omitted the field.
384 pub originator_info: OriginatorInfo,
385}
386
387impl EnvelopedData {
388 /// Round-18: surface the originator-side cert + CRL chain when the
389 /// envelope carried `[0] IMPLICIT OriginatorInfo`. Returns `None`
390 /// when the field was absent or both `certs[]`/`crls[]` were empty.
391 pub fn originator_info(&self) -> Option<&OriginatorInfo> {
392 if self.originator_info.is_empty() {
393 None
394 } else {
395 Some(&self.originator_info)
396 }
397 }
398}
399
400/// Parse the `ContentInfo` envelope wrapping an `EnvelopedData` blob,
401/// returning the inner parsed structure.
402pub fn parse_envelope(data: &[u8]) -> Result<EnvelopedData, PdfError> {
403 // ContentInfo ::= SEQUENCE { contentType OID, content [0] EXPLICIT ANY }
404 let (body, rest) = read_sequence(data)?;
405 if !rest.is_empty() {
406 return Err(PdfError::other(
407 "CMS: trailing bytes after ContentInfo SEQUENCE",
408 ));
409 }
410 let (oid, rest) = read_oid(body)?;
411 if oid != OID_ENVELOPED_DATA {
412 return Err(PdfError::other(format!(
413 "CMS: ContentInfo contentType must be id-envelopedData (got {oid:?})"
414 )));
415 }
416 let (content, rest) = read_context(rest, 0)?;
417 if !rest.is_empty() {
418 return Err(PdfError::other(
419 "CMS: trailing bytes after [0] EXPLICIT content",
420 ));
421 }
422 parse_enveloped_data(content)
423}
424
425/// Parse a bare `EnvelopedData` SEQUENCE (no surrounding ContentInfo).
426pub fn parse_enveloped_data(data: &[u8]) -> Result<EnvelopedData, PdfError> {
427 // EnvelopedData ::= SEQUENCE {
428 // version CMSVersion,
429 // originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
430 // recipientInfos SET OF RecipientInfo,
431 // encryptedContentInfo EncryptedContentInfo,
432 // unprotectedAttrs [1] IMPLICIT UnprotectedAttributes OPTIONAL
433 // }
434 let (body, rest) = read_sequence(data)?;
435 if !rest.is_empty() {
436 return Err(PdfError::other(
437 "CMS: trailing bytes after EnvelopedData SEQUENCE",
438 ));
439 }
440 let (version, body) = read_integer_u64(body)?;
441 if version > 4 {
442 return Err(PdfError::other(format!(
443 "CMS: unsupported EnvelopedData version {version}"
444 )));
445 }
446 // `[0] IMPLICIT OriginatorInfo OPTIONAL` — round 18 captures it.
447 // Body shape per RFC 5652 §10.2.1 / §10.2.2:
448 // OriginatorInfo ::= SEQUENCE {
449 // certs [0] IMPLICIT CertificateSet OPTIONAL,
450 // crls [1] IMPLICIT RevocationInfoChoices OPTIONAL
451 // }
452 // The `[0] IMPLICIT OriginatorInfo` outer wrapper means the body
453 // bytes are themselves the SEQUENCE contents (the IMPLICIT tag
454 // replaces the SEQUENCE's universal tag).
455 let (orig_opt, body) = maybe_read_context(body, 0)?;
456 let originator_info = match orig_opt {
457 Some(b) => parse_originator_info(b)?,
458 None => OriginatorInfo::default(),
459 };
460
461 // RecipientInfos
462 let (ri_set, body) = read_set(body)?;
463 let mut recipients = Vec::new();
464 let mut all_recipients = Vec::new();
465 let mut cursor = ri_set;
466 while !cursor.is_empty() {
467 let (parsed, tail) = parse_recipient_info(cursor)?;
468 if let Some(p) = parsed {
469 if let RecipientInfoVariant::KeyTrans(ktri) = &p {
470 recipients.push(ktri.clone());
471 }
472 all_recipients.push(p);
473 }
474 cursor = tail;
475 }
476 if all_recipients.is_empty() {
477 return Err(PdfError::other(
478 "CMS: EnvelopedData has no recognised RecipientInfo entries",
479 ));
480 }
481
482 // EncryptedContentInfo ::= SEQUENCE {
483 // contentType OBJECT IDENTIFIER,
484 // contentEncryptionAlgorithm AlgorithmIdentifier,
485 // encryptedContent [0] IMPLICIT OCTET STRING OPTIONAL
486 // }
487 let (eci, body) = read_sequence(body)?;
488 let (_ct_oid, eci_rest) = read_oid(eci)?;
489 let (alg_seq, eci_rest) = read_sequence(eci_rest)?;
490 let (alg_oid, alg_params) = read_oid(alg_seq)?;
491 let content_encryption = decode_content_alg(&alg_oid, alg_params)?;
492 // `[0] IMPLICIT OCTET STRING` — context-specific, primitive form.
493 let (enc_body, eci_rest) = read_expected(eci_rest, Class::ContextSpecific, 0)?;
494 if enc_body.constructed {
495 return Err(PdfError::other(
496 "CMS: encryptedContent constructed-form not supported",
497 ));
498 }
499 if !eci_rest.is_empty() {
500 return Err(PdfError::other(
501 "CMS: trailing bytes after EncryptedContentInfo",
502 ));
503 }
504 let encrypted_content = enc_body.body.to_vec();
505
506 // The trailing unprotectedAttrs is OPTIONAL — discard if present.
507 let _ = maybe_read_context(body, 1)?;
508
509 Ok(EnvelopedData {
510 recipients,
511 all_recipients,
512 content_encryption,
513 encrypted_content,
514 originator_info,
515 })
516}
517
518/// Round-18: parse an `OriginatorInfo` body (the bytes inside the
519/// `[0] IMPLICIT` wrapper of `EnvelopedData`). Both the `certs[0]` and
520/// `crls[1]` fields are OPTIONAL `IMPLICIT` SETs of `CertificateChoices` /
521/// `RevocationInfoChoices`. We capture every entry's raw DER bytes
522/// (including the outer tag/length) so callers can re-parse them
523/// without reconstructing the encoding.
524fn parse_originator_info(body: &[u8]) -> Result<OriginatorInfo, PdfError> {
525 let mut cursor = body;
526 let mut info = OriginatorInfo::default();
527 // `[0] IMPLICIT CertificateSet` — an IMPLICIT SET, so its tag is
528 // `[0]` constructed (the SET's universal tag is replaced).
529 if !cursor.is_empty() {
530 let (peek, _) = super::der::read_tlv(cursor)?;
531 if peek.class == Class::ContextSpecific && peek.tag_number == 0 {
532 let (set_body, after) = super::der::read_tlv(cursor)?;
533 info.certs = split_set_into_raw_entries(set_body.body)?;
534 cursor = after;
535 }
536 }
537 // `[1] IMPLICIT RevocationInfoChoices` — same wrapping.
538 if !cursor.is_empty() {
539 let (peek, _) = super::der::read_tlv(cursor)?;
540 if peek.class == Class::ContextSpecific && peek.tag_number == 1 {
541 let (set_body, after) = super::der::read_tlv(cursor)?;
542 info.crls = split_set_into_raw_entries(set_body.body)?;
543 cursor = after;
544 }
545 }
546 if !cursor.is_empty() {
547 return Err(PdfError::other(
548 "CMS: trailing bytes after OriginatorInfo SEQUENCE body",
549 ));
550 }
551 Ok(info)
552}
553
554/// Split a SET body (or any concatenation of TLVs) into a Vec where
555/// each entry is the raw DER bytes (tag + length + body) of one TLV.
556/// Used by [`parse_originator_info`] to surface `certs[]` / `crls[]`
557/// alternatives without dispatching on the inner CHOICE.
558fn split_set_into_raw_entries(set_body: &[u8]) -> Result<Vec<Vec<u8>>, PdfError> {
559 let mut out = Vec::new();
560 let mut cursor = set_body;
561 while !cursor.is_empty() {
562 let before_len = cursor.len();
563 let (_tlv, after) = super::der::read_tlv(cursor)?;
564 let consumed = before_len - after.len();
565 out.push(cursor[..consumed].to_vec());
566 cursor = after;
567 }
568 Ok(out)
569}
570
571/// Parse a single `RecipientInfo` element from the SET body. Returns
572/// `Ok(None)` for variants this implementation doesn't recognise
573/// (`[2] kekri`, `[3] pwri`, `[4] ori`); KTRI (untagged SEQUENCE) and
574/// KARI (`[1]` IMPLICIT) are surfaced as separate enum arms.
575fn parse_recipient_info(data: &[u8]) -> Result<(Option<RecipientInfoVariant>, &[u8]), PdfError> {
576 // Peek at the tag to decide which CHOICE branch we're in.
577 let (peek, peek_tail) = super::der::read_tlv(data)?;
578 if peek.class == Class::ContextSpecific {
579 match peek.tag_number {
580 1 => {
581 // [1] IMPLICIT KeyAgreeRecipientInfo — body is the
582 // KARI SEQUENCE contents (the implicit tag replaces
583 // the SEQUENCE's universal tag).
584 let kari = parse_kari(peek.body)?;
585 return Ok((Some(RecipientInfoVariant::KeyAgree(kari)), peek_tail));
586 }
587 // [2] kekri, [3] pwri, [4] ori — skipped silently.
588 _ => return Ok((None, peek_tail)),
589 }
590 }
591 // Otherwise it's a KeyTransRecipientInfo SEQUENCE.
592 let (ktri_body, tail) = read_sequence(data)?;
593 let (version, after_ver) = read_integer_u64(ktri_body)?;
594 // version 0 (uses IssuerAndSerialNumber) and 2 (uses
595 // SubjectKeyIdentifier) per RFC 5652 §6.2.1. Only v0 has the
596 // matching info our public-key handler can use.
597 if version > 2 {
598 return Err(PdfError::other(format!(
599 "CMS: unsupported KeyTransRecipientInfo version {version}"
600 )));
601 }
602 // RecipientIdentifier ::= CHOICE {
603 // issuerAndSerialNumber IssuerAndSerialNumber, -- SEQUENCE
604 // subjectKeyIdentifier [0] SubjectKeyIdentifier -- OCTET STRING
605 // }
606 let (rid, after_rid) = if version == 0 {
607 let (ias_body, rest) = read_sequence(after_ver)?;
608 // IssuerAndSerialNumber ::= SEQUENCE { issuer Name, serial INTEGER }
609 // We need the raw DER of `issuer` to byte-compare against the
610 // user's certificate's `issuer`, so reconstruct the slice
611 // including its tag+length header by computing the offset
612 // from `ias_body` to the start of the `serialNumber` TLV.
613 let (issuer_tlv, ias_after_issuer) = super::der::read_tlv(ias_body)?;
614 if issuer_tlv.class != Class::Universal
615 || issuer_tlv.tag_number != super::der::tag::SEQUENCE
616 {
617 return Err(PdfError::other(
618 "CMS: IssuerAndSerialNumber.issuer must be a SEQUENCE",
619 ));
620 }
621 let issuer_total = ias_body.len() - ias_after_issuer.len();
622 let issuer_der = ias_body[..issuer_total].to_vec();
623 let (serial_body, _) = read_integer_bytes(ias_after_issuer)?;
624 (
625 RecipientId::IssuerAndSerial(IssuerAndSerial {
626 issuer_der,
627 serial: serial_body.to_vec(),
628 }),
629 rest,
630 )
631 } else {
632 // [0] IMPLICIT OCTET STRING — context-specific primitive
633 // wrapping the recipient's SubjectKeyIdentifier (RFC 5652
634 // §6.2.1). The body bytes are the raw 20-byte SHA-1 of the
635 // recipient cert's `SubjectPublicKeyInfo` BIT STRING contents
636 // (RFC 5280 §4.2.1.2 method 1).
637 let (tlv, rest) = super::der::read_tlv(after_ver)?;
638 if tlv.class != Class::ContextSpecific || tlv.tag_number != 0 {
639 return Err(PdfError::other(format!(
640 "CMS: KeyTransRecipientInfo[v=2] expects [0] SubjectKeyIdentifier, got class={:?} tag={}",
641 tlv.class, tlv.tag_number
642 )));
643 }
644 if tlv.constructed {
645 return Err(PdfError::other(
646 "CMS: SubjectKeyIdentifier must be primitive [0] IMPLICIT OCTET STRING",
647 ));
648 }
649 (RecipientId::SubjectKeyIdentifier(tlv.body.to_vec()), rest)
650 };
651 // KeyEncryptionAlgorithm ::= AlgorithmIdentifier
652 let (alg_seq, after_alg) = read_sequence(after_rid)?;
653 let (kea_oid, _alg_params) = read_oid(alg_seq)?;
654 if kea_oid != OID_RSA_ENCRYPTION {
655 return Err(PdfError::other(format!(
656 "CMS: unsupported KeyEncryptionAlgorithm {kea_oid:?} (only rsaEncryption)"
657 )));
658 }
659 let (enc_key, after_key) = read_octet_string(after_alg)?;
660 if !after_key.is_empty() {
661 return Err(PdfError::other(
662 "CMS: trailing bytes after KeyTransRecipientInfo.encryptedKey",
663 ));
664 }
665 Ok((
666 Some(RecipientInfoVariant::KeyTrans(KeyTransRecipientInfo {
667 rid,
668 key_encryption_oid: kea_oid,
669 encrypted_key: enc_key.to_vec(),
670 })),
671 tail,
672 ))
673}
674
675/// Parse the body of a `[1] IMPLICIT KeyAgreeRecipientInfo` per RFC
676/// 5652 §6.2.2. The implicit tag replaces the SEQUENCE's universal
677/// tag, so `data` here is the KARI's body bytes — the same shape we
678/// would otherwise see *inside* a `read_sequence(...)` call.
679///
680/// ```asn.1
681/// KeyAgreeRecipientInfo ::= SEQUENCE {
682/// version CMSVersion, -- always 3
683/// originator [0] EXPLICIT OriginatorIdentifierOrKey,
684/// ukm [1] EXPLICIT UserKeyingMaterial OPTIONAL,
685/// keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
686/// recipientEncryptedKeys RecipientEncryptedKeys
687/// }
688/// ```
689fn parse_kari(data: &[u8]) -> Result<KeyAgreeRecipientInfo, PdfError> {
690 let (version, body) = read_integer_u64(data)?;
691 if version != 3 {
692 return Err(PdfError::other(format!(
693 "CMS: KeyAgreeRecipientInfo version must be 3 (got {version})"
694 )));
695 }
696 // [0] EXPLICIT OriginatorIdentifierOrKey
697 let (orig_body, body) = read_context(body, 0)?;
698 let originator = parse_originator(orig_body)?;
699 // [1] EXPLICIT UserKeyingMaterial OPTIONAL
700 let (ukm_opt, body) = maybe_read_context(body, 1)?;
701 let ukm = match ukm_opt {
702 Some(b) => {
703 // The UKM body is itself an OCTET STRING.
704 let (ukm_bytes, rest) = read_octet_string(b)?;
705 if !rest.is_empty() {
706 return Err(PdfError::other(
707 "CMS: KARI ukm context wrapper has trailing bytes",
708 ));
709 }
710 ukm_bytes.to_vec()
711 }
712 None => Vec::new(),
713 };
714 // KeyEncryptionAlgorithmIdentifier
715 let (alg_body, body) = read_sequence(body)?;
716 let (kea_oid, alg_params) = read_oid(alg_body)?;
717 // recipientEncryptedKeys SEQUENCE OF RecipientEncryptedKey
718 let (rek_body, body) = read_sequence(body)?;
719 if !body.is_empty() {
720 return Err(PdfError::other(
721 "CMS: KARI has trailing bytes after recipientEncryptedKeys",
722 ));
723 }
724 let mut recipient_encrypted_keys = Vec::new();
725 let mut cursor = rek_body;
726 while !cursor.is_empty() {
727 let (rek, tail) = parse_recipient_encrypted_key(cursor)?;
728 recipient_encrypted_keys.push(rek);
729 cursor = tail;
730 }
731 if recipient_encrypted_keys.is_empty() {
732 return Err(PdfError::other("CMS: KARI recipientEncryptedKeys is empty"));
733 }
734 Ok(KeyAgreeRecipientInfo {
735 originator,
736 ukm,
737 key_encryption_oid: kea_oid,
738 key_encryption_params: alg_params.to_vec(),
739 recipient_encrypted_keys,
740 })
741}
742
743/// Parse `OriginatorIdentifierOrKey` (RFC 5652 §6.2.2).
744///
745/// ```asn.1
746/// OriginatorIdentifierOrKey ::= CHOICE {
747/// issuerAndSerialNumber IssuerAndSerialNumber,
748/// subjectKeyIdentifier [0] SubjectKeyIdentifier,
749/// originatorKey [1] OriginatorPublicKey
750/// }
751/// ```
752fn parse_originator(data: &[u8]) -> Result<OriginatorId, PdfError> {
753 let (peek, _) = super::der::read_tlv(data)?;
754 if peek.class == Class::ContextSpecific {
755 match peek.tag_number {
756 0 => {
757 // [0] IMPLICIT SubjectKeyIdentifier (OCTET STRING).
758 if peek.constructed {
759 return Err(PdfError::other(
760 "CMS: KARI originator [0] SKI must be primitive",
761 ));
762 }
763 Ok(OriginatorId::SubjectKeyIdentifier(peek.body.to_vec()))
764 }
765 1 => {
766 // [1] IMPLICIT OriginatorPublicKey — body is the SPKI
767 // SEQUENCE contents.
768 let opk = parse_originator_public_key(peek.body)?;
769 Ok(OriginatorId::OriginatorKey(opk))
770 }
771 other => Err(PdfError::other(format!(
772 "CMS: KARI originator unknown context-tag {other}"
773 ))),
774 }
775 } else {
776 // Untagged SEQUENCE — IssuerAndSerialNumber.
777 let (ias_body, rest) = read_sequence(data)?;
778 if !rest.is_empty() {
779 return Err(PdfError::other(
780 "CMS: KARI originator IAS has trailing bytes",
781 ));
782 }
783 let (issuer_tlv, ias_after_issuer) = super::der::read_tlv(ias_body)?;
784 if issuer_tlv.class != Class::Universal
785 || issuer_tlv.tag_number != super::der::tag::SEQUENCE
786 {
787 return Err(PdfError::other(
788 "CMS: KARI originator IAS issuer must be a SEQUENCE",
789 ));
790 }
791 let issuer_total = ias_body.len() - ias_after_issuer.len();
792 let issuer_der = ias_body[..issuer_total].to_vec();
793 let (serial_body, _) = read_integer_bytes(ias_after_issuer)?;
794 Ok(OriginatorId::IssuerAndSerial(IssuerAndSerial {
795 issuer_der,
796 serial: serial_body.to_vec(),
797 }))
798 }
799}
800
801/// Parse `OriginatorPublicKey` (RFC 5652 §6.2.2). Body shape is
802/// `SEQUENCE { algorithm AlgorithmIdentifier, publicKey BIT STRING }`.
803fn parse_originator_public_key(data: &[u8]) -> Result<OriginatorPublicKey, PdfError> {
804 let (alg_body, after_alg) = read_sequence(data)?;
805 let (alg_oid, alg_params) = read_oid(alg_body)?;
806 // BIT STRING — body has a leading unused-bits byte we drop.
807 let (bs, rest) = super::der::read_tlv(after_alg)?;
808 if bs.class != Class::Universal || bs.tag_number != super::der::tag::BIT_STRING {
809 return Err(PdfError::other(
810 "CMS: KARI OriginatorPublicKey expects BIT STRING for publicKey",
811 ));
812 }
813 if !rest.is_empty() {
814 return Err(PdfError::other(
815 "CMS: KARI OriginatorPublicKey has trailing bytes",
816 ));
817 }
818 if bs.body.is_empty() {
819 return Err(PdfError::other(
820 "CMS: KARI OriginatorPublicKey BIT STRING empty",
821 ));
822 }
823 Ok(OriginatorPublicKey {
824 algorithm_oid: alg_oid,
825 algorithm_params: alg_params.to_vec(),
826 public_key: bs.body[1..].to_vec(),
827 })
828}
829
830/// Parse one `RecipientEncryptedKey` (RFC 5652 §6.2.2).
831///
832/// ```asn.1
833/// RecipientEncryptedKey ::= SEQUENCE {
834/// rid KeyAgreeRecipientIdentifier,
835/// encryptedKey EncryptedKey
836/// }
837/// KeyAgreeRecipientIdentifier ::= CHOICE {
838/// issuerAndSerialNumber IssuerAndSerialNumber,
839/// rKeyId [0] IMPLICIT RecipientKeyIdentifier
840/// }
841/// RecipientKeyIdentifier ::= SEQUENCE {
842/// subjectKeyIdentifier SubjectKeyIdentifier,
843/// date GeneralizedTime OPTIONAL,
844/// other OtherKeyAttribute OPTIONAL
845/// }
846/// ```
847fn parse_recipient_encrypted_key(data: &[u8]) -> Result<(RecipientEncryptedKey, &[u8]), PdfError> {
848 let (rek_body, tail) = read_sequence(data)?;
849 let (peek, _) = super::der::read_tlv(rek_body)?;
850 let (rid, after_rid) = if peek.class == Class::ContextSpecific && peek.tag_number == 0 {
851 // [0] IMPLICIT RecipientKeyIdentifier — body is the RKID's
852 // SEQUENCE contents. Consume the [0] TLV from the parent
853 // body to compute `after`, then peel its body for the SKI +
854 // the round-18 OPTIONAL `date` and `other` fields.
855 //
856 // RecipientKeyIdentifier ::= SEQUENCE {
857 // subjectKeyIdentifier SubjectKeyIdentifier, -- OCTET STRING
858 // date GeneralizedTime OPTIONAL,
859 // other OtherKeyAttribute OPTIONAL
860 // }
861 let (rkid_tlv, after) = super::der::read_tlv(rek_body)?;
862 let (ski, after_ski) = read_octet_string(rkid_tlv.body)?;
863 let mut cursor = after_ski;
864 let mut date: Option<Vec<u8>> = None;
865 // GeneralizedTime is universal tag 24 (RFC 5280 §4.1.2.5.2).
866 const TAG_GENERALIZED_TIME: u32 = 24;
867 if !cursor.is_empty() {
868 let (peek_dt, _) = super::der::read_tlv(cursor)?;
869 if peek_dt.class == Class::Universal && peek_dt.tag_number == TAG_GENERALIZED_TIME {
870 let (dt_tlv, after_dt) = super::der::read_tlv(cursor)?;
871 date = Some(dt_tlv.body.to_vec());
872 cursor = after_dt;
873 }
874 }
875 let mut other: Option<OtherKeyAttribute> = None;
876 if !cursor.is_empty() {
877 // Either the next TLV is the `other` SEQUENCE, or there is
878 // trailing junk we should reject. RFC 5652 §6.2.2 marks
879 // `other` as a SEQUENCE with `keyAttrId` OID + optional
880 // `keyAttr` ANY.
881 let (peek_other, _) = super::der::read_tlv(cursor)?;
882 if peek_other.class == Class::Universal
883 && peek_other.tag_number == super::der::tag::SEQUENCE
884 {
885 let (oka_body, after_oka) = read_sequence(cursor)?;
886 let (oid_arcs, after_oid) = read_oid(oka_body)?;
887 let key_attr = after_oid.to_vec();
888 other = Some(OtherKeyAttribute {
889 key_attr_id: oid_arcs,
890 key_attr,
891 });
892 cursor = after_oka;
893 } else {
894 return Err(PdfError::other(format!(
895 "CMS: RecipientKeyIdentifier trailing TLV class={:?} tag={} \
896 is neither GeneralizedTime nor OtherKeyAttribute SEQUENCE",
897 peek_other.class, peek_other.tag_number,
898 )));
899 }
900 }
901 if !cursor.is_empty() {
902 return Err(PdfError::other(
903 "CMS: RecipientKeyIdentifier has trailing bytes after \
904 (subjectKeyIdentifier, date?, other?)",
905 ));
906 }
907 (
908 KeyAgreeRecipientId::RecipientKeyIdentifier {
909 ski: ski.to_vec(),
910 date,
911 other,
912 },
913 after,
914 )
915 } else {
916 // Untagged SEQUENCE → IssuerAndSerialNumber.
917 let (ias_body, after) = read_sequence(rek_body)?;
918 let (issuer_tlv, ias_after_issuer) = super::der::read_tlv(ias_body)?;
919 if issuer_tlv.class != Class::Universal
920 || issuer_tlv.tag_number != super::der::tag::SEQUENCE
921 {
922 return Err(PdfError::other(
923 "CMS: KARI REK IAS issuer must be a SEQUENCE",
924 ));
925 }
926 let issuer_total = ias_body.len() - ias_after_issuer.len();
927 let issuer_der = ias_body[..issuer_total].to_vec();
928 let (serial_body, _) = read_integer_bytes(ias_after_issuer)?;
929 (
930 KeyAgreeRecipientId::IssuerAndSerial(IssuerAndSerial {
931 issuer_der,
932 serial: serial_body.to_vec(),
933 }),
934 after,
935 )
936 };
937 let (enc_key, after_key) = read_octet_string(after_rid)?;
938 if !after_key.is_empty() {
939 return Err(PdfError::other(
940 "CMS: KARI RecipientEncryptedKey has trailing bytes",
941 ));
942 }
943 Ok((
944 RecipientEncryptedKey {
945 rid,
946 encrypted_key: enc_key.to_vec(),
947 },
948 tail,
949 ))
950}
951
952fn decode_content_alg(oid: &[u64], params: &[u8]) -> Result<ContentEncryption, PdfError> {
953 if oid == OID_RC4 {
954 // No parameters, or a NULL.
955 Ok(ContentEncryption::Rc4)
956 } else if oid == OID_AES128_CBC || oid == OID_AES256_CBC {
957 // Parameters are an OCTET STRING wrapping the IV.
958 let (iv, _) = read_octet_string(params)?;
959 if iv.len() != 16 {
960 return Err(PdfError::other(format!(
961 "CMS: AES-CBC IV must be 16 bytes (got {})",
962 iv.len()
963 )));
964 }
965 let mut iv_arr = [0u8; 16];
966 iv_arr.copy_from_slice(iv);
967 if oid == OID_AES128_CBC {
968 Ok(ContentEncryption::Aes128Cbc { iv: iv_arr })
969 } else {
970 Ok(ContentEncryption::Aes256Cbc { iv: iv_arr })
971 }
972 } else if oid == OID_RC2_CBC {
973 // Round-17: RC2-CBC. Parameters per RFC 3370 §5.1:
974 // RC2CBCParameter ::= SEQUENCE {
975 // rc2ParameterVersion INTEGER (0..255) DEFAULT 32, -- effective-key version
976 // iv OCTET STRING (size 8)
977 // }
978 // Some legacy writers omit the parameter version; treat that as
979 // the DEFAULT 32 (mapped per RFC 2268 §6 to 32 effective bits).
980 // Other writers wrap params as a bare OCTET STRING (RFC 2268
981 // §6 wire form) — accept both.
982 let (param_seq, after_seq) = match read_sequence(params) {
983 Ok(parts) => parts,
984 Err(_) => {
985 // Bare OCTET STRING fallback (RFC 2268 §6).
986 let (iv_bytes, _) = read_octet_string(params)?;
987 if iv_bytes.len() != 8 {
988 return Err(PdfError::other(format!(
989 "CMS: RC2-CBC bare-OCTET-STRING IV must be 8 bytes (got {})",
990 iv_bytes.len()
991 )));
992 }
993 let mut iv_arr = [0u8; 8];
994 iv_arr.copy_from_slice(iv_bytes);
995 return Ok(ContentEncryption::Rc2Cbc {
996 effective_key_bits: 32,
997 iv: iv_arr,
998 });
999 }
1000 };
1001 let _ = after_seq;
1002 // SEQUENCE-wrapped params: optional INTEGER (rc2ParameterVersion),
1003 // mandatory OCTET STRING (iv).
1004 let mut cursor = param_seq;
1005 let mut effective_key_bits = 32u32;
1006 let (peek, _) = super::der::read_tlv(cursor)?;
1007 if peek.class == super::der::Class::Universal && peek.tag_number == super::der::tag::INTEGER
1008 {
1009 let (vers_u64, after) = read_integer_u64(cursor)?;
1010 // RFC 2268 §6 mapping: 160 → 40 bits, 120 → 64 bits, 58 →
1011 // 128 bits. Other values pass through as the literal
1012 // effective-key bit count (RFC 3370 §5.1's 32 default).
1013 effective_key_bits = match vers_u64 {
1014 160 => 40,
1015 120 => 64,
1016 58 => 128,
1017 v if v <= 255 => v as u32,
1018 _ => {
1019 return Err(PdfError::other(format!(
1020 "CMS: RC2 rc2ParameterVersion {vers_u64} out of range (RFC 2268 §6)"
1021 )))
1022 }
1023 };
1024 cursor = after;
1025 }
1026 let (iv_bytes, rest) = read_octet_string(cursor)?;
1027 if !rest.is_empty() {
1028 return Err(PdfError::other(
1029 "CMS: RC2-CBC parameters trailing bytes after IV",
1030 ));
1031 }
1032 if iv_bytes.len() != 8 {
1033 return Err(PdfError::other(format!(
1034 "CMS: RC2-CBC IV must be 8 bytes (got {})",
1035 iv_bytes.len()
1036 )));
1037 }
1038 let mut iv_arr = [0u8; 8];
1039 iv_arr.copy_from_slice(iv_bytes);
1040 Ok(ContentEncryption::Rc2Cbc {
1041 effective_key_bits,
1042 iv: iv_arr,
1043 })
1044 } else if oid == OID_DES_EDE3_CBC {
1045 // Round-17: 3DES-CBC. Parameters per RFC 3370 §5.2 / RFC 5652
1046 // §12.4: OCTET STRING (size 8) — the 8-byte CBC IV.
1047 let (iv_bytes, _) = read_octet_string(params)?;
1048 if iv_bytes.len() != 8 {
1049 return Err(PdfError::other(format!(
1050 "CMS: DES-EDE3-CBC IV must be 8 bytes (got {})",
1051 iv_bytes.len()
1052 )));
1053 }
1054 let mut iv_arr = [0u8; 8];
1055 iv_arr.copy_from_slice(iv_bytes);
1056 Ok(ContentEncryption::DesEde3Cbc { iv: iv_arr })
1057 } else {
1058 Err(PdfError::other(format!(
1059 "CMS: unsupported contentEncryptionAlgorithm {oid:?}"
1060 )))
1061 }
1062}
1063
1064#[cfg(test)]
1065mod tests {
1066 use super::*;
1067 use crate::pubsec::cms_build::{build_envelope_aes256, RecipientPlain};
1068
1069 #[test]
1070 fn parse_handcrafted_aes256_envelope() {
1071 // Build a minimal envelope with one synthetic recipient.
1072 let issuer_der = super::super::der::write_sequence(b"");
1073 let recipient =
1074 RecipientPlain::ias(issuer_der.clone(), vec![0x01, 0x02, 0x03], vec![0xAA; 256]);
1075 let plaintext =
1076 b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13";
1077 let envelope = build_envelope_aes256(&[recipient], plaintext, &[0xBBu8; 32], &[0xCCu8; 16]);
1078 let parsed = parse_envelope(&envelope).expect("parse envelope");
1079 assert_eq!(parsed.recipients.len(), 1);
1080 match &parsed.recipients[0].rid {
1081 super::RecipientId::IssuerAndSerial(ias) => {
1082 assert_eq!(ias.serial, vec![0x01, 0x02, 0x03]);
1083 assert_eq!(ias.issuer_der, issuer_der);
1084 }
1085 other => panic!("unexpected rid: {other:?}"),
1086 }
1087 match parsed.content_encryption {
1088 ContentEncryption::Aes256Cbc { iv } => assert_eq!(iv, [0xCC; 16]),
1089 _ => panic!("expected AES256CBC"),
1090 }
1091 }
1092}