Skip to main content

structured_cms/
personal.rs

1//! Provider-neutral personal key containers (RFC7292 / RFC5958).
2//!
3//! Reading is inspection, never authentication or permission to use a key.
4//! [`process()`] authenticates the complete original safe before extraction;
5//! passwords, cryptographic engines, key selection and trust remain explicit
6//! caller contracts. Private-key values are borrowed, not decoded by a backend.
7//! No type containing private plaintext implements `Debug` or allocating Clone.
8
9use crate::{ContentInfo, Error, ErrorCode, Octets, Result, SignedData};
10use crate::{attribute::Attribute, enveloped::EncodedValue, signer::required};
11use structured_asn1::memory::{OwnedVec, Work};
12use structured_asn1::{Element, Integer, Oid, Reader, Tag, universal};
13use structured_pkix::AlgorithmIdentifier;
14
15mod process;
16pub mod protection;
17pub use process::{
18    Backend, Cause, Decision, Failure, Fragments, Origin, Plaintext, Source, legacy_password,
19    process,
20};
21
22const FIELD: &str = "personal";
23const DATA: Oid<'static> = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 7, 1]);
24const SIGNED: Oid<'static> = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 7, 2]);
25const ENCRYPTED: Oid<'static> = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 7, 6]);
26
27/// Original PFX integrity mode. Absence of a MAC is not authenticated data.
28pub enum Integrity<'a> {
29    /// Password integrity over the exact logical `AuthenticatedSafe` octets.
30    Password {
31        /// BER `AuthenticatedSafe` encoding, excluding OCTET STRING headers.
32        safe: Octets<'a>,
33        /// Optional original MAC; None remains unauthenticated inspection.
34        mac: Option<MacData<'a>>,
35    },
36    /// Public-key integrity. The existing CMS processor owns signature rules.
37    Signed(SignedData<'a>),
38}
39
40/// Original PFX v3. Parsing does not invoke passwords or provider operations.
41pub struct Pfx<'a> {
42    integrity: Integrity<'a>,
43    original: EncodedValue<'a>,
44}
45impl<'a> Pfx<'a> {
46    /// Reads one complete BER/DER PFX at the producing source position.
47    pub fn read(parent: &mut Reader<'a>) -> Result<Self> {
48        let element = required(parent, FIELD)?;
49        let value = Self::read_element(parent, &element)?;
50        end(parent)?;
51        Ok(value)
52    }
53    /// Reads an embedded original PFX, without rejecting enclosing siblings.
54    pub fn read_element(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
55        let mut inside = sequence(parent, element)?;
56        let version = number(&mut inside)?;
57        if version.as_u64() != Some(3) {
58            return Err(invalid(ErrorCode::UnsupportedVersion, element));
59        }
60        let envelope = required(&mut inside, FIELD)?;
61        let (info, reader) = ContentInfo::read_element(&inside, &envelope)?;
62        let mac = inside
63            .next_element()
64            .map_err(encoding)?
65            .map(|value| MacData::read(&inside, &value))
66            .transpose()?;
67        end(&inside)?;
68        let integrity = if equal_oid(&inside, info.kind(), DATA)? {
69            Integrity::Password {
70                safe: Octets::read(&reader, info.content(), FIELD)?,
71                mac,
72            }
73        } else if equal_oid(&inside, info.kind(), SIGNED)? {
74            // RFC7292 §4: MacData occurs ONLY in password integrity mode.
75            // https://www.rfc-editor.org/rfc/rfc7292#section-4
76            if mac.is_some() {
77                return Err(invalid(ErrorCode::FieldOrder, element));
78            }
79            let signed = SignedData::read_element(&reader, info.content())?;
80            if !equal_oid(&reader, signed.content().kind(), DATA)? {
81                return Err(invalid(ErrorCode::UnexpectedContentType, info.content()));
82            }
83            if signed.content().content().is_none() {
84                return Err(invalid(ErrorCode::DetachedContent, info.content()));
85            }
86            Integrity::Signed(signed)
87        } else {
88            return Err(invalid(ErrorCode::UnexpectedContentType, &envelope));
89        };
90        Ok(Self {
91            integrity,
92            original: EncodedValue::new(parent, *element),
93        })
94    }
95    /// Whole original PFX and its lazy original/current reader boundary.
96    #[must_use]
97    pub fn original(&self) -> &EncodedValue<'a> {
98        &self.original
99    }
100    /// Original immutable integrity mode and its exact protected data.
101    #[must_use]
102    pub fn integrity(&self) -> &Integrity<'a> {
103        &self.integrity
104    }
105    pub(crate) fn safe(&self) -> &Octets<'a> {
106        match &self.integrity {
107            Integrity::Password { safe, .. } => safe,
108            Integrity::Signed(signed) => signed.content().content().expect("checked attached PFX"),
109        }
110    }
111}
112
113/// Original `MacData`, including ignored legacy fields in PBMAC1 mode.
114pub struct MacData<'a> {
115    /// Original digest/MAC algorithm; PBMAC1 is distinguished explicitly.
116    pub algorithm: AlgorithmIdentifier<'a>,
117    /// Encoded expected MAC octets.
118    pub digest: Octets<'a>,
119    /// Original legacy salt. RFC9879 §4 requires ignoring it for PBMAC1.
120    pub salt: Octets<'a>,
121    /// Original legacy iteration count. None denotes schema DEFAULT 1.
122    /// In PBMAC1 mode this field is ignored, not used as a KDF limit/input.
123    pub iterations: Option<Integer<'a>>,
124    /// Validated PBMAC1 parameters, or None for the legacy MAC scheme.
125    pub pbmac1: Option<protection::Pair<'a>>,
126}
127impl<'a> MacData<'a> {
128    /// Reads `MacData` and RFC9879 mandatory PBMAC1 parameter constraints.
129    pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
130        let mut inside = sequence(parent, element)?;
131        let digest_info = required(&mut inside, FIELD)?;
132        let mut digest_info = sequence(&inside, &digest_info)?;
133        let algorithm = algorithm(&mut digest_info)?;
134        let value = required(&mut digest_info, FIELD)?;
135        let digest = Octets::read(&digest_info, &value, FIELD)?;
136        end(&digest_info)?;
137        let value = required(&mut inside, FIELD)?;
138        let salt = Octets::read(&inside, &value, FIELD)?;
139        let iterations = if inside.is_empty() {
140            None
141        } else {
142            Some(number(&mut inside)?)
143        };
144        end(&inside)?;
145        // RFC9879 §4 ignores this field for MAC computation, not for DER
146        // encoding. X.690 §11.5 still omits the schema DEFAULT in either mode.
147        if let Some(rounds) = iterations {
148            default_one(parent, rounds, element)?;
149        }
150        let pbmac1 = if equal_oid(parent, algorithm.algorithm(), protection::PBMAC1)? {
151            let pair = protection::Pair::read(algorithm, parent)?;
152            if let Some(width) = protection::hmac_width(pair.scheme, parent)?
153                && digest.len() != width
154            {
155                return Err(invalid(ErrorCode::DigestWidth, element));
156            }
157            if let Some(kdf) = pair.pbkdf2.as_ref()
158                && kdf.key_length.is_none()
159            {
160                return Err(invalid(ErrorCode::MissingField, element));
161            }
162            Some(pair)
163        } else {
164            match structured_pkix::algorithm::digest::algorithm(&algorithm, &parent.execution())
165                .map_err(Error::pkix)?
166            {
167                structured_asn1::digest::Resolution::Supported(hash)
168                    if digest.len() != hash.width() =>
169                {
170                    return Err(invalid(ErrorCode::DigestWidth, element));
171                }
172                structured_asn1::digest::Resolution::Malformed => {
173                    return Err(invalid(ErrorCode::AlgorithmSyntax, element));
174                }
175                _ => {}
176            }
177            if let Some(rounds) = iterations {
178                positive(rounds, element)?;
179            }
180            None
181        };
182        Ok(Self {
183            algorithm,
184            digest,
185            salt,
186            iterations,
187            pbmac1,
188        })
189    }
190}
191
192/// One protected `AuthenticatedSafe` member. Unrecognized content stays explicit.
193pub enum Safe<'a> {
194    /// Unencrypted `SafeContents` BER encoding, including its SEQUENCE TLV.
195    Data(Octets<'a>),
196    /// Password-encrypted `SafeContents`.
197    Encrypted(EncryptedData<'a>),
198    /// Public-key encrypted `SafeContents`, reusing all common recipient types.
199    Enveloped(crate::enveloped::EnvelopedData<'a>),
200    /// An open content type without a schema owned by this format.
201    Other {
202        /// Original content type.
203        kind: Oid<'a>,
204        /// Original inner value and producing context.
205        value: EncodedValue<'a>,
206    },
207}
208impl<'a> Safe<'a> {
209    /// Reads one original `ContentInfo` member; no cryptography is performed.
210    pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
211        let (info, reader) = ContentInfo::read_element(parent, element)?;
212        if equal_oid(parent, info.kind(), DATA)? {
213            Ok(Self::Data(Octets::read(&reader, info.content(), FIELD)?))
214        } else if equal_oid(parent, info.kind(), ENCRYPTED)? {
215            Ok(Self::Encrypted(EncryptedData::read(
216                &reader,
217                info.content(),
218            )?))
219        } else if equal_oid(parent, info.kind(), crate::enveloped::IDENTIFIER)? {
220            Ok(Self::Enveloped(
221                crate::enveloped::EnvelopedData::read_element(&reader, info.content())?,
222            ))
223        } else {
224            Ok(Self::Other {
225                kind: info.kind(),
226                value: EncodedValue::new(&reader, *info.content()),
227            })
228        }
229    }
230}
231
232/// CMS `EncryptedData`, RFC5652 §8, used by PKCS#12 password privacy.
233pub struct EncryptedData<'a> {
234    /// Original content algorithm and attached/detached encrypted octets.
235    pub content: crate::enveloped::EncryptedContent<'a>,
236    /// Every original unprotected attribute, not authenticated bag metadata.
237    pub attributes: Option<OwnedVec<Attribute<'a>>>,
238    /// Parsed password-protection parameters, prepared once before callbacks.
239    pub protection: protection::Protection<'a>,
240}
241impl<'a> EncryptedData<'a> {
242    /// Reads a complete original `EncryptedData` SEQUENCE and version rule.
243    pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
244        let mut inside = sequence(parent, element)?;
245        let version = number(&mut inside)?;
246        let info = required(&mut inside, FIELD)?;
247        let content = crate::enveloped::encrypted_content(&inside, &info)?;
248        let attributes = inside
249            .next_element()
250            .map_err(encoding)?
251            .map(|set| attributes(&inside, &set, Tag::context(true, 1)))
252            .transpose()?;
253        end(&inside)?;
254        if version.as_u64() != Some(if attributes.is_some() { 2 } else { 0 }) {
255            return Err(invalid(ErrorCode::VersionMismatch, element));
256        }
257        if attributes.as_ref().is_some_and(|a| a.is_empty()) {
258            return Err(invalid(ErrorCode::EmptyAttributes, element));
259        }
260        let protection = protection::Protection::read(content.algorithm, parent)?;
261        Ok(Self {
262            content,
263            attributes,
264            protection,
265        })
266    }
267}
268
269/// RFC5958 `OneAsymmetricKey` / PKCS#8 `PrivateKeyInfo`.
270pub struct PrivateKey<'a> {
271    /// Complete original key container and producing reader context.
272    pub original: EncodedValue<'a>,
273    /// Zero is v1; one is v2. Validated against public-key presence.
274    pub version: u64,
275    /// Original algorithm, not a key/backend selection.
276    pub algorithm: AlgorithmIdentifier<'a>,
277    /// Original secret key contents. Borrowed, including BER fragments.
278    pub secret: Octets<'a>,
279    /// Original optional key attributes, distinct from bag attributes.
280    pub attributes: Option<OwnedVec<Attribute<'a>>>,
281    /// Validated original IMPLICIT BIT STRING, including BER segmentation.
282    pub public_key: Option<EncodedValue<'a>>,
283}
284impl<'a> PrivateKey<'a> {
285    /// Reads the original key container without normalizing or copying secrets.
286    pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
287        let mut inside = sequence(parent, element)?;
288        let version = number(&mut inside)?
289            .as_u64()
290            .ok_or_else(|| invalid(ErrorCode::ValueTooLarge, element))?;
291        let algorithm = algorithm(&mut inside)?;
292        let value = required(&mut inside, FIELD)?;
293        let secret = Octets::read(&inside, &value, FIELD)?;
294        let mut next = inside.next_element().map_err(encoding)?;
295        let attributes = if let Some(set) = next.filter(|v| v.tag() == Tag::context(true, 0)) {
296            let attributes = attributes(&inside, &set, Tag::context(true, 0))?;
297            next = inside.next_element().map_err(encoding)?;
298            Some(attributes)
299        } else {
300            None
301        };
302        let public_key = if let Some(bits) = next {
303            if bits.tag() != Tag::context(false, 1) && bits.tag() != Tag::context(true, 1) {
304                return Err(invalid(ErrorCode::UnexpectedTag, &bits));
305            }
306            structured_pkix::der::implicit_bit_string(&inside, &bits, FIELD)
307                .map_err(Error::pkix)?;
308            Some(EncodedValue::new(&inside, bits))
309        } else {
310            None
311        };
312        end(&inside)?;
313        if version != u64::from(public_key.is_some()) {
314            return Err(invalid(ErrorCode::VersionMismatch, element));
315        }
316        Ok(Self {
317            original: EncodedValue::new(parent, *element),
318            version,
319            algorithm,
320            secret,
321            attributes,
322            public_key,
323        })
324    }
325}
326
327/// `EncryptedPrivateKeyInfo`, with original protection parameters/ciphertext.
328pub struct ShroudedKey<'a> {
329    /// Original protection algorithm.
330    pub algorithm: AlgorithmIdentifier<'a>,
331    /// Original encrypted private-key contents.
332    pub encrypted: Octets<'a>,
333    /// Structural password-protection interpretation; unknown remains open.
334    pub protection: protection::Protection<'a>,
335}
336impl<'a> ShroudedKey<'a> {
337    /// Reads the whole original encrypted key, without KDF/decryption work.
338    pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
339        let mut inside = sequence(parent, element)?;
340        let algorithm = algorithm(&mut inside)?;
341        let value = required(&mut inside, FIELD)?;
342        let encrypted = Octets::read(&inside, &value, FIELD)?;
343        end(&inside)?;
344        let protection = protection::Protection::read(algorithm, parent)?;
345        Ok(Self {
346            algorithm,
347            encrypted,
348            protection,
349        })
350    }
351}
352
353/// A typed original bag value, including open types and nested bags.
354pub enum BagValue<'a> {
355    /// Plain PKCS#8 key.
356    Key(PrivateKey<'a>),
357    /// Shrouded PKCS#8 key.
358    Shrouded(ShroudedKey<'a>),
359    /// X.509 certificate bytes (not implicitly parsed/authenticated).
360    Certificate(Octets<'a>),
361    /// SDSI base64 certificate text with shared IA5 syntax validation.
362    Sdsi(Octets<'a>),
363    /// X.509 CRL bytes (not implicitly authenticated).
364    Crl(Octets<'a>),
365    /// A registered or unknown secret value.
366    Secret {
367        /// Original secret-type OID.
368        kind: Oid<'a>,
369        /// Whole original open value.
370        value: EncodedValue<'a>,
371    },
372    /// An open bag/certificate/CRL type, without guessed normalization.
373    Other {
374        /// Original type OID.
375        kind: Oid<'a>,
376        /// Whole original open value.
377        value: EncodedValue<'a>,
378    },
379    /// Nested `SafeContents`, visited iteratively by the processor.
380    Contents(EncodedValue<'a>),
381}
382/// `SafeBag` with original identity/attributes and decoded value.
383pub struct Bag<'a> {
384    /// Original bag identifier, including unknown extensions.
385    pub kind: Oid<'a>,
386    /// Original whole bag encoding and producing context.
387    pub original: EncodedValue<'a>,
388    /// Parsed original value, never a product-selected key.
389    pub value: BagValue<'a>,
390    /// Optional original attributes, including repeated/empty unknown values.
391    pub attributes: Option<OwnedVec<Attribute<'a>>>,
392}
393impl<'a> Bag<'a> {
394    /// Reads one complete original `SafeBag`. All six standard alternatives use
395    /// the existing shared field kernels, not a second BER grammar.
396    pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
397        let mut inside = sequence(parent, element)?;
398        let kind = inside.read_oid().map_err(encoding)?;
399        let wrapper = inside.expect(Tag::context(true, 0)).map_err(encoding)?;
400        let mut explicit = inside.enter(&wrapper).map_err(encoding)?;
401        let value = required(&mut explicit, FIELD)?;
402        end(&explicit)?;
403        let attributes = inside
404            .next_element()
405            .map_err(encoding)?
406            .map(|set| attributes(&inside, &set, Tag::constructed(universal::SET)))
407            .transpose()?;
408        end(&inside)?;
409        // RFC7292 §4.2: BAG-TYPE is extensible. Unknown does not mean KeyBag.
410        let value = if bag_id(parent, kind, 1)? {
411            BagValue::Key(PrivateKey::read(&explicit, &value)?)
412        } else if bag_id(parent, kind, 2)? {
413            BagValue::Shrouded(ShroudedKey::read(&explicit, &value)?)
414        } else if bag_id(parent, kind, 6)? {
415            tag(&value, Tag::constructed(universal::SEQUENCE))?;
416            BagValue::Contents(EncodedValue::new(&explicit, value))
417        } else if bag_id(parent, kind, 3)? || bag_id(parent, kind, 4)? || bag_id(parent, kind, 5)? {
418            let mut typed = sequence(&explicit, &value)?;
419            let inner_kind = typed.read_oid().map_err(encoding)?;
420            let wrapper = typed.expect(Tag::context(true, 0)).map_err(encoding)?;
421            let mut inner = typed.enter(&wrapper).map_err(encoding)?;
422            let content = required(&mut inner, FIELD)?;
423            end(&inner)?;
424            end(&typed)?;
425            let certificate = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 9, 22, 1]);
426            let sdsi = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 9, 22, 2]);
427            let crl = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 9, 23, 1]);
428            if bag_id(parent, kind, 5)? {
429                structured_pkix::der::conforms(&inner, &content, FIELD).map_err(Error::pkix)?;
430                BagValue::Secret {
431                    kind: inner_kind,
432                    value: EncodedValue::new(&inner, content),
433                }
434            } else if bag_id(parent, kind, 3)? && equal_oid(parent, inner_kind, certificate)? {
435                BagValue::Certificate(Octets::read(&inner, &content, FIELD)?)
436            } else if bag_id(parent, kind, 3)? && equal_oid(parent, inner_kind, sdsi)? {
437                BagValue::Sdsi(Octets::read_ia5(&inner, &content, FIELD)?)
438            } else if bag_id(parent, kind, 4)? && equal_oid(parent, inner_kind, crl)? {
439                BagValue::Crl(Octets::read(&inner, &content, FIELD)?)
440            } else {
441                structured_pkix::der::conforms(&inner, &content, FIELD).map_err(Error::pkix)?;
442                BagValue::Other {
443                    kind: inner_kind,
444                    value: EncodedValue::new(&inner, content),
445                }
446            }
447        } else {
448            structured_pkix::der::conforms(&explicit, &value, FIELD).map_err(Error::pkix)?;
449            BagValue::Other {
450                kind,
451                value: EncodedValue::new(&explicit, value),
452            }
453        };
454        Ok(Self {
455            kind,
456            original: EncodedValue::new(parent, *element),
457            value,
458            attributes,
459        })
460    }
461}
462
463fn bag_id(parent: &Reader<'_>, oid: Oid<'_>, last: u8) -> Result<bool> {
464    equal_oid(
465        parent,
466        oid,
467        Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 12, 10, 1, last]),
468    )
469}
470pub(super) fn equal_oid(parent: &Reader<'_>, left: Oid<'_>, right: Oid<'_>) -> Result<bool> {
471    parent.charge(1).map_err(encoding)?;
472    if left.as_bytes().len() != right.as_bytes().len() {
473        return Ok(false);
474    }
475    parent
476        .charge(left.as_bytes().len() as u64)
477        .map_err(encoding)?;
478    Ok(left == right)
479}
480fn attributes<'a>(
481    parent: &Reader<'a>,
482    set: &Element<'a>,
483    expected: Tag,
484) -> Result<OwnedVec<Attribute<'a>>> {
485    tag(set, expected)?;
486    let mut inside = parent.enter(set).map_err(encoding)?;
487    let execution = parent.execution();
488    let mut out = execution.vector();
489    let mut previous = None;
490    while let Some(value) = inside.next_element().map_err(encoding)? {
491        if inside.encoding().is_der() {
492            if let Some(bytes) = previous
493                && !structured_pkix::der::set_ordered(&inside, bytes, &value, FIELD)
494                    .map_err(Error::pkix)?
495            {
496                return Err(invalid(ErrorCode::SetOrder, &value));
497            }
498            previous = Some(value.as_bytes());
499        }
500        let attribute = Attribute::read(&inside, &value, FIELD)?;
501        // DER values were already walked by Attribute::read. BER inspection
502        // must reopen the saved value context: the list cursor is two levels
503        // above attrValues and would grant nested values those levels for free.
504        if !inside.encoding().is_der() {
505            for (ordinal, item) in attribute.values().iter().enumerate() {
506                let reader = attribute
507                    .value_reader_in(ordinal, &inside, FIELD)?
508                    .expect("ordinal comes from this attribute's original values");
509                structured_pkix::der::conforms(&reader, item, FIELD).map_err(Error::pkix)?;
510            }
511        }
512        out.try_push(attribute)
513            .map_err(|e| Error::resource(e, FIELD, value.span().start))?;
514    }
515    Ok(out.into_owned())
516}
517pub(super) fn sequence<'a>(parent: &Reader<'a>, element: &Element<'a>) -> Result<Reader<'a>> {
518    tag(element, Tag::constructed(universal::SEQUENCE))?;
519    parent.enter(element).map_err(encoding)
520}
521pub(super) fn tag(element: &Element<'_>, expected: Tag) -> Result<()> {
522    if element.tag() != expected {
523        return Err(invalid(ErrorCode::UnexpectedTag, element));
524    }
525    Ok(())
526}
527pub(super) fn end(parent: &Reader<'_>) -> Result<()> {
528    if !parent.is_empty() {
529        return Err(Error::schema(
530            ErrorCode::TrailingData,
531            FIELD,
532            parent.offset(),
533        ));
534    }
535    Ok(())
536}
537pub(super) fn number<'a>(parent: &mut Reader<'a>) -> Result<Integer<'a>> {
538    parent.read_integer().map_err(encoding)
539}
540pub(super) fn positive(value: Integer<'_>, element: &Element<'_>) -> Result<()> {
541    if value.is_negative() || value.as_u64() == Some(0) {
542        return Err(invalid(ErrorCode::MalformedValue, element));
543    }
544    Ok(())
545}
546fn default_one(parent: &Reader<'_>, value: Integer<'_>, element: &Element<'_>) -> Result<()> {
547    if parent.encoding().is_der() && value.as_u64() == Some(1) {
548        return Err(invalid(ErrorCode::DefaultWritten, element));
549    }
550    Ok(())
551}
552pub(super) fn algorithm<'a>(parent: &mut Reader<'a>) -> Result<AlgorithmIdentifier<'a>> {
553    let element = required(parent, FIELD)?;
554    structured_pkix::algorithm::read(parent, &element, FIELD).map_err(Error::pkix)
555}
556pub(super) fn invalid(code: ErrorCode, element: &Element<'_>) -> Error {
557    Error::schema(code, FIELD, element.span().start)
558}
559pub(super) fn encoding(error: structured_asn1::Error) -> Error {
560    Error::encoding(error, FIELD)
561}