Skip to main content

structured_cades/
properties.rs

1//! Original optional property values. Open credential/policy semantics stay
2//! explicit obligations; reading an ASN.1 envelope never authenticates them.
3
4use crate::{Error, Result};
5use structured_asn1::memory::{OwnedVec, Work};
6use structured_asn1::{Element, Oid, Reader, Span, Tag, universal};
7use structured_cms::{ErrorCode, Octets, attribute::Attribute, oid, signer::SignerInfo};
8use structured_pkix::algorithm::{self, AlgorithmIdentifier};
9
10pub(crate) mod legacy;
11pub mod values;
12
13/// Complete original value diagnostic. Processing refusals are never retained
14/// here as malformed syntax: they terminate before a partial report escapes.
15#[derive(Debug)]
16pub struct Fault {
17    /// Original attribute type whose value failed.
18    pub attribute: Oid<'static>,
19    /// Field used by the format's single schema reader.
20    pub field: &'static str,
21    /// Original shared/schema error, including its source coordinates.
22    pub cause: Error,
23}
24
25/// Open-format original input requiring its actual format owner. This is not
26/// an authenticated credential or a caller-writable positive baseline fact.
27#[derive(Debug, Clone, Copy)]
28pub struct OpenValue<'a> {
29    /// Outer format service that owns this value.
30    pub role: OpenRole,
31    /// Original type, when the open schema carries an identifier.
32    pub kind: Option<Oid<'a>>,
33    /// Whole original open pair/attribute, including an absent OPTIONAL value.
34    pub enclosure: Element<'a>,
35    /// Original payload context, or explicit absence. The enclosing pair is
36    /// never substituted for a missing open value.
37    pub value: Option<crate::source::Value<'a>>,
38}
39
40/// Distinct services cannot substitute for each other during composition.
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum OpenRole {
43    /// Claimed (unauthenticated) signer attribute.
44    Claimed,
45    /// X.509 attribute certificate with its own authority/material requirements.
46    AttributeCertificate,
47    /// An independently specified non-X.509 attribute certificate.
48    OtherAttributeCertificate,
49    /// An independently authenticated assertion (including signed SAML).
50    SignedAssertion,
51    /// A commitment qualifier whose semantics its OID defines.
52    CommitmentQualifier,
53    /// A signature-policy qualifier whose semantics its OID defines.
54    PolicyQualifier,
55}
56
57/// Original signature-policy identifier and declared document digest. The
58/// signature-policy document's technical specification determines the hashing
59/// input; an all-zero digest means UNKNOWN, never the empty document's digest.
60pub struct Policy<'a> {
61    /// Original signature policy OID.
62    pub identifier: Oid<'a>,
63    /// Original written digest mechanism and parameter provenance.
64    pub algorithm: AlgorithmIdentifier<'a>,
65    /// Original declared digest, including constructed BER fragments.
66    pub hash: Octets<'a>,
67    /// Normative zero-hash marker, independent of output width (§5.2.9.1).
68    pub hash_unknown: bool,
69}
70
71/// Original policy store, without filesystem/network discovery or acquisition.
72pub struct Store<'a> {
73    /// Original technical specification choice (OID or `IA5String` URI).
74    pub specification: Element<'a>,
75    /// Original embedded document or explicit caller-resolved local URI.
76    pub document: crate::source::Value<'a>,
77}
78
79/// One complete original signer's optional property reading. Unknown open
80/// values stay visible, and a failing value does not hide later occurrences.
81pub struct Properties<'a> {
82    faults: OwnedVec<Fault>,
83    open: OwnedVec<OpenValue<'a>>,
84    issuer_hints: OwnedVec<Span>,
85    policy: Option<Policy<'a>>,
86    store: Option<Store<'a>>,
87    values: Option<values::Values<'a>>,
88}
89
90impl<'a> Properties<'a> {
91    /// Read each recognized value under its original attribute reader. Existing
92    /// CMS placement/cardinality and signed-DER checks remain the CMS kernel's
93    /// responsibility; registered legacy cardinality and value schemas from
94    /// EN319122-1 Annex A/D are checked here for every signer, including TSAs.
95    /// This also checks baseline attribute encoding. A finding does not forbid
96    /// independent CMS decoding or mathematical verification.
97    /// Reference timestamp envelopes and carried `TSTInfo` are inspected by
98    /// full preparation, retaining mapped diagnostics with the original source.
99    pub fn read(signer: &SignerInfo<'a>, parent: &Reader<'a>) -> Result<Self> {
100        signer.require_session(parent, "cades.properties")?;
101        let work = parent.execution();
102        let mut policy = None;
103        let mut store = None;
104        let mut values = None;
105        let mut faults = work.vector();
106        let mut open = work.vector();
107        let mut issuer_hints = work.vector();
108        for set in [signer.signed_attributes(), signer.unsigned_attributes()]
109            .into_iter()
110            .flatten()
111        {
112            for attribute in set.items() {
113                // Fixed registered identifiers are at most twelve octets; a
114                // different width is rejected by OID equality before scanning.
115                parent
116                    .charge(11 * (1 + attribute.kind().as_bytes().len().min(12) as u64))
117                    .map_err(|cause| Error::encoding(cause, "cades.properties"))?;
118                let Some((kind, field)) = known(attribute.kind()) else {
119                    legacy::inspect(
120                        attribute,
121                        parent,
122                        &mut faults,
123                        &mut issuer_hints,
124                        &mut values,
125                        &work,
126                    )?;
127                    continue;
128                };
129                let reading = read_value(
130                    attribute,
131                    kind,
132                    field,
133                    parent,
134                    &mut open,
135                    &mut policy,
136                    &mut store,
137                );
138                if let Err(cause) = reading {
139                    if cause.is_terminal() {
140                        return Err(cause);
141                    }
142                    let at = cause.offset();
143                    faults
144                        .try_push(Fault {
145                            attribute: kind,
146                            field,
147                            cause,
148                        })
149                        .map_err(|cause| Error::resource(cause, field, at))?;
150                }
151            }
152        }
153        Ok(Self {
154            faults: faults.into_owned(),
155            open: open.into_owned(),
156            issuer_hints: issuer_hints.into_owned(),
157            policy,
158            store,
159            values: values.map(values::Values::into_owned),
160        })
161    }
162    /// Every located value-schema failure, retaining its original cause.
163    #[must_use]
164    pub fn faults(&self) -> &[Fault] {
165        &self.faults
166    }
167    pub(crate) fn issuer_hints(&self) -> &[Span] {
168        &self.issuer_hints
169    }
170    /// All original open semantic inputs, not only successfully bound ones.
171    #[must_use]
172    pub fn open_values(&self) -> &[OpenValue<'a>] {
173        &self.open
174    }
175    /// Parsed written policy; no hash comparison or policy acceptance implied.
176    #[must_use]
177    pub fn policy(&self) -> Option<&Policy<'a>> {
178        self.policy.as_ref()
179    }
180    /// Parsed original store; no file or URI is accessed by reading it.
181    #[must_use]
182    pub fn store(&self) -> Option<&Store<'a>> {
183        self.store.as_ref()
184    }
185    /// Original legacy validation models, retained once without copying their
186    /// bytes. An empty written value set remains distinct from absence.
187    pub fn legacy_values(&self) -> Option<&values::Values<'a>> {
188        self.values.as_ref()
189    }
190
191    pub(crate) fn has_legacy_values(signer: &SignerInfo<'_>, parent: &Reader<'_>) -> Result<bool> {
192        for set in [signer.signed_attributes(), signer.unsigned_attributes()]
193            .into_iter()
194            .flatten()
195        {
196            for attribute in set.items() {
197                parent
198                    .charge(2 * (1 + attribute.kind().as_bytes().len().min(11) as u64))
199                    .map_err(|cause| Error::encoding(cause, "cades.legacy"))?;
200                if attribute.kind() == legacy::LEGACY[2] || attribute.kind() == legacy::LEGACY[3] {
201                    return Ok(true);
202                }
203            }
204        }
205        Ok(false)
206    }
207}
208
209/// Whether an attribute belongs to the `CAdES` schemas recognized by this core.
210/// Profiles which permit only a subset use the same identifier catalogue;
211/// this does not validate placement, cardinality or an attribute value.
212pub fn defines(
213    kind: Oid<'_>,
214    work: &impl Work,
215) -> core::result::Result<bool, structured_asn1::memory::MemoryError> {
216    work.charge_work(11 * (1 + kind.as_bytes().len().min(12) as u64))?;
217    if known(kind).is_some() {
218        return Ok(true);
219    }
220    for expected in legacy::LEGACY.into_iter().chain([
221        oid::CONTENT_TYPE,
222        oid::MESSAGE_DIGEST,
223        oid::SIGNING_TIME,
224        oid::SIGNING_CERTIFICATE,
225        oid::SIGNING_CERTIFICATE_V2,
226        oid::CONTENT_TIMESTAMP,
227        oid::ARCHIVE_TIMESTAMP_V3,
228        oid::ATS_HASH_INDEX_V3,
229        oid::CLAIMED_SAML_ASSERTION,
230    ]) {
231        work.charge_work(1 + kind.as_bytes().len().min(expected.as_bytes().len()) as u64)?;
232        if kind == expected {
233            return Ok(true);
234        }
235    }
236    work.charge_work(1 + kind.as_bytes().len().min(11) as u64)?;
237    Ok(crate::preservation::Kind::from_oid(kind).is_some())
238}
239
240fn known(kind: Oid<'_>) -> Option<(Oid<'static>, &'static str)> {
241    for (expected, field) in [
242        (oid::COMMITMENT_TYPE_INDICATION, "commitmentTypeIndication"),
243        (oid::MIME_TYPE, "mimeType"),
244        (oid::CONTENT_HINTS, "contentHints"),
245        (oid::CONTENT_IDENTIFIER, "contentIdentifier"),
246        (oid::CONTENT_REFERENCE, "contentReference"),
247        (oid::SIGNER_LOCATION, "signerLocation"),
248        (oid::SIGNER_ATTRIBUTES_V2, "signerAttributesV2"),
249        (
250            oid::SIGNATURE_POLICY_IDENTIFIER,
251            "signaturePolicyIdentifier",
252        ),
253        (oid::SIGNATURE_POLICY_STORE, "signaturePolicyStore"),
254        (oid::SIGNATURE_TIMESTAMP, "signatureTimeStamp"),
255        (oid::COUNTERSIGNATURE, "countersignature"),
256    ] {
257        if kind == expected {
258            return Some((expected, field));
259        }
260    }
261    None
262}
263
264fn read_value<'a, W: Work>(
265    attribute: &Attribute<'a>,
266    kind: Oid<'static>,
267    field: &'static str,
268    parent: &Reader<'a>,
269    open: &mut OwnedVec<OpenValue<'a>, W>,
270    policy: &mut Option<Policy<'a>>,
271    store: &mut Option<Store<'a>>,
272) -> Result<()> {
273    if kind == oid::COUNTERSIGNATURE {
274        return if attribute.values().is_empty() {
275            Err(syntax(field, attribute.span().start))
276        } else {
277            Ok(())
278        };
279    }
280    if kind == oid::SIGNATURE_TIMESTAMP {
281        // EN319122-1 §6.3 Table1(l), not §5.3, requires DER for the WHOLE
282        // signature-time-stamp attribute. §5.3 only specifies its token type.
283        // This reports baseline conformance; generic CMS BER decoding and
284        // independent root/token signature verification remain available.
285        // https://www.etsi.org/deliver/etsi_en/319100_319199/31912201/01.03.01_60/en_31912201v010301p.pdf
286        let mut der = parent
287            .reread(attribute.element(), structured_asn1::Encoding::Der)
288            .map_err(|cause| Error::encoding(cause, field))?;
289        let element = der
290            .expect(Tag::constructed(universal::SEQUENCE))
291            .map_err(|cause| Error::encoding(cause, field))?;
292        return structured_pkix::der::conforms(&der, &element, field).map_err(Error::Pkix);
293    }
294    if attribute.values().len() != 1 {
295        return Err(syntax(field, attribute.span().start));
296    }
297    let mut reader = attribute
298        .value_reader_in(0, parent, field)?
299        .ok_or_else(|| syntax(field, attribute.span().start))?;
300    let value = reader
301        .next_element()
302        .map_err(|cause| Error::encoding(cause, field))?
303        .ok_or_else(|| syntax(field, reader.offset()))?;
304    if !reader.is_empty() {
305        return Err(syntax(field, reader.offset()));
306    }
307    if kind == oid::MIME_TYPE {
308        require_tag(&value, Tag::primitive(universal::UTF8_STRING), field)?;
309        structured_pkix::der::conforms(&reader, &value, field).map_err(Error::Pkix)?;
310        let valid =
311            structured_cms::mime::media_type_or_headers(value.contents(), &reader.execution())
312                .map_err(|cause| Error::resource(cause, field, value.span().start))?;
313        return if valid {
314            Ok(())
315        } else {
316            Err(syntax(field, value.span().start))
317        };
318    }
319    if kind == oid::CONTENT_IDENTIFIER {
320        Octets::read(&reader, &value, field)?;
321        return Ok(());
322    }
323    let inside = sequence(&reader, &value, field)?;
324    read_sequence(inside, kind, field, open, policy, store)
325}
326
327fn read_sequence<'a, W: Work>(
328    mut inside: Reader<'a>,
329    kind: Oid<'static>,
330    field: &'static str,
331    open: &mut OwnedVec<OpenValue<'a>, W>,
332    policy: &mut Option<Policy<'a>>,
333    store: &mut Option<Store<'a>>,
334) -> Result<()> {
335    if kind == oid::CONTENT_HINTS {
336        let first = next(&mut inside, field)?;
337        if first.tag() == Tag::primitive(universal::UTF8_STRING) {
338            string(&inside, &first, field)?;
339            inside
340                .read_oid()
341                .map_err(|cause| Error::encoding(cause, field))?;
342        } else {
343            inside
344                .oid(&first)
345                .map_err(|cause| Error::encoding(cause, field))?;
346        }
347    } else if kind == oid::CONTENT_REFERENCE {
348        inside
349            .read_oid()
350            .map_err(|cause| Error::encoding(cause, field))?;
351        for _ in 0..2 {
352            let part = next(&mut inside, field)?;
353            Octets::read(&inside, &part, field)?;
354        }
355    } else if kind == oid::COMMITMENT_TYPE_INDICATION {
356        inside
357            .read_oid()
358            .map_err(|cause| Error::encoding(cause, field))?;
359        if let Some(qualifiers) = inside
360            .next_element()
361            .map_err(|cause| Error::encoding(cause, field))?
362        {
363            let mut values = sequence(&inside, &qualifiers, field)?;
364            if values.is_empty() {
365                return Err(syntax(field, qualifiers.span().start));
366            }
367            while let Some(value) = values
368                .next_element()
369                .map_err(|cause| Error::encoding(cause, field))?
370            {
371                open_pair(&values, &value, OpenRole::CommitmentQualifier, field, open)?;
372            }
373        }
374    } else if kind == oid::SIGNER_LOCATION {
375        location(&mut inside, field)?;
376    } else if kind == oid::SIGNER_ATTRIBUTES_V2 {
377        attributes(&mut inside, field, open)?;
378    } else if kind == oid::SIGNATURE_POLICY_IDENTIFIER {
379        let identifier = inside
380            .read_oid()
381            .map_err(|cause| Error::encoding(cause, field))?;
382        let hash_value = next(&mut inside, field)?;
383        let mut hash_fields = sequence(&inside, &hash_value, field)?;
384        let id = next(&mut hash_fields, field)?;
385        let algorithm = algorithm::read(&hash_fields, &id, field).map_err(Error::Pkix)?;
386        let hash_value = next(&mut hash_fields, field)?;
387        let hash = Octets::read(&hash_fields, &hash_value, field)?;
388        end(&hash_fields, field)?;
389        let mut hash_unknown = true;
390        for piece in hash.pieces() {
391            inside
392                .charge(1 + piece.as_bytes().len() as u64)
393                .map_err(|cause| Error::encoding(cause, field))?;
394            if piece.as_bytes().iter().any(|byte| *byte != 0) {
395                hash_unknown = false;
396            }
397        }
398        if let Some(qualifiers) = inside
399            .next_element()
400            .map_err(|cause| Error::encoding(cause, field))?
401        {
402            let mut values = sequence(&inside, &qualifiers, field)?;
403            if values.is_empty() {
404                return Err(syntax(field, qualifiers.span().start));
405            }
406            while let Some(value) = values
407                .next_element()
408                .map_err(|cause| Error::encoding(cause, field))?
409            {
410                open_pair(&values, &value, OpenRole::PolicyQualifier, field, open)?;
411            }
412        }
413        *policy = Some(Policy {
414            identifier,
415            algorithm,
416            hash,
417            hash_unknown,
418        });
419    } else if kind == oid::SIGNATURE_POLICY_STORE {
420        let specification = next(&mut inside, field)?;
421        spec(&inside, &specification, field)?;
422        let document = next(&mut inside, field)?;
423        if document.tag().number() == Some(universal::OCTET_STRING) {
424            Octets::read(&inside, &document, field)?;
425        } else {
426            // EN319122-1 §5.2.10 selects a local-store URI, not arbitrary
427            // IA5 text. Syntax validation never fetches or resolves the URI.
428            // https://www.etsi.org/deliver/etsi_en/319100_319199/31912201/01.03.01_60/en_31912201v010301p.pdf
429            uri_value(&inside, &document, field)?;
430        }
431        *store = Some(Store {
432            specification,
433            document: crate::source::Value::of(&inside, document, field),
434        });
435    }
436    end(&inside, field)
437}
438
439fn location(reader: &mut Reader<'_>, field: &'static str) -> Result<()> {
440    // Annex D uses EXPLICIT TAGS, even though the containing CMS is BER.
441    // At least one field and postalAddress SIZE(1..6) are format constraints.
442    let mut previous = None;
443    while let Some(value) = reader
444        .next_element()
445        .map_err(|cause| Error::encoding(cause, field))?
446    {
447        let Some(number) = value.tag().number() else {
448            return Err(syntax(field, value.span().start));
449        };
450        if number > 2
451            || value.tag() != Tag::context(true, number)
452            || previous.is_some_and(|p| number <= p)
453        {
454            return Err(syntax(field, value.span().start));
455        }
456        previous = Some(number);
457        let mut explicit = reader
458            .enter(&value)
459            .map_err(|cause| Error::encoding(cause, field))?;
460        let part = next(&mut explicit, field)?;
461        end(&explicit, field)?;
462        if number < 2 {
463            directory_string(&explicit, &part, field)?;
464        } else {
465            let mut lines = sequence(&explicit, &part, field)?;
466            let mut count = 0;
467            while let Some(line) = lines
468                .next_element()
469                .map_err(|cause| Error::encoding(cause, field))?
470            {
471                count += 1;
472                if count > 6 {
473                    return Err(syntax(field, line.span().start));
474                }
475                directory_string(&lines, &line, field)?;
476            }
477            if count == 0 {
478                return Err(syntax(field, part.span().start));
479            }
480        }
481    }
482    if previous.is_none() {
483        return Err(syntax(field, reader.offset()));
484    }
485    Ok(())
486}
487
488fn claimed_value(claimed: &Attribute<'_>, parent: &Reader<'_>, field: &'static str) -> Result<()> {
489    parent
490        .charge(
491            1 + claimed
492                .kind()
493                .as_bytes()
494                .len()
495                .min(oid::CLAIMED_SAML_ASSERTION.as_bytes().len()) as u64,
496        )
497        .map_err(|cause| Error::encoding(cause, field))?;
498    if claimed.kind() != oid::CLAIMED_SAML_ASSERTION {
499        return Ok(());
500    }
501    // EN319122-1 §5.2.6.2 requires exactly one OCTET STRING. Reading
502    // this envelope neither interprets nor authenticates the SAML claim.
503    // https://www.etsi.org/deliver/etsi_en/319100_319199/31912201/01.03.01_60/en_31912201v010301p.pdf
504    claimed.single(field)?;
505    let mut reader = claimed
506        .value_reader_in(0, parent, field)?
507        .ok_or_else(|| syntax(field, claimed.span().start))?;
508    let value = next(&mut reader, field)?;
509    Octets::read(&reader, &value, field)?;
510    Ok(())
511}
512
513fn attributes<'a, W: Work>(
514    reader: &mut Reader<'a>,
515    field: &'static str,
516    open: &mut OwnedVec<OpenValue<'a>, W>,
517) -> Result<()> {
518    let mut previous = None;
519    let mut populated = false;
520    while let Some(value) = reader
521        .next_element()
522        .map_err(|cause| Error::encoding(cause, field))?
523    {
524        let Some(number) = value.tag().number() else {
525            return Err(syntax(field, value.span().start));
526        };
527        if number > 2
528            || value.tag() != Tag::context(true, number)
529            || previous.is_some_and(|p| number <= p)
530        {
531            return Err(syntax(field, value.span().start));
532        }
533        previous = Some(number);
534        let mut explicit = reader
535            .enter(&value)
536            .map_err(|cause| Error::encoding(cause, field))?;
537        let part = next(&mut explicit, field)?;
538        end(&explicit, field)?;
539        let mut entries = sequence(&explicit, &part, field)?;
540        // §5.2.6.1 requires nonempty certified and assertion sequences.
541        if number != 0 && entries.is_empty() {
542            return Err(syntax(field, part.span().start));
543        }
544        while let Some(entry) = entries
545            .next_element()
546            .map_err(|cause| Error::encoding(cause, field))?
547        {
548            populated = true;
549            if number == 0 {
550                let claimed = Attribute::read(&entries, &entry, field)?;
551                claimed_value(&claimed, &entries, field)?;
552                push(
553                    open,
554                    OpenValue {
555                        role: OpenRole::Claimed,
556                        kind: Some(claimed.kind()),
557                        enclosure: entry,
558                        value: Some(crate::source::Value::of(&entries, entry, field)),
559                    },
560                    field,
561                )?;
562            } else if number == 1 {
563                let choice = entry.tag();
564                if choice != Tag::context(true, 0) && choice != Tag::context(true, 1) {
565                    return Err(syntax(field, entry.span().start));
566                }
567                let mut explicit = entries
568                    .enter(&entry)
569                    .map_err(|cause| Error::encoding(cause, field))?;
570                let certificate = next(&mut explicit, field)?;
571                end(&explicit, field)?;
572                if choice.number() == Some(0) {
573                    require_tag(&certificate, Tag::constructed(universal::SEQUENCE), field)?;
574                    push(
575                        open,
576                        OpenValue {
577                            role: OpenRole::AttributeCertificate,
578                            kind: None,
579                            enclosure: certificate,
580                            value: Some(crate::source::Value::of(&explicit, certificate, field)),
581                        },
582                        field,
583                    )?;
584                } else {
585                    open_pair(
586                        &explicit,
587                        &certificate,
588                        OpenRole::OtherAttributeCertificate,
589                        field,
590                        open,
591                    )?;
592                }
593            } else {
594                open_pair(&entries, &entry, OpenRole::SignedAssertion, field, open)?;
595            }
596        }
597    }
598    // EN319122-1 §5.2.6.1 forbids an empty signer-attributes-v2, not
599    // merely an absent wrapper. An empty claimedAttributes is allowed only
600    // when another field actually contributes an entry.
601    // https://www.etsi.org/deliver/etsi_en/319100_319199/31912201/01.03.01_60/en_31912201v010301p.pdf
602    if !populated {
603        return Err(syntax(field, reader.offset()));
604    }
605    Ok(())
606}
607
608fn open_pair<'a, W: Work>(
609    parent: &Reader<'a>,
610    element: &Element<'a>,
611    role: OpenRole,
612    field: &'static str,
613    open: &mut OwnedVec<OpenValue<'a>, W>,
614) -> Result<()> {
615    let mut pair = sequence(parent, element, field)?;
616    let kind = pair
617        .read_oid()
618        .map_err(|cause| Error::encoding(cause, field))?;
619    let value = pair
620        .next_element()
621        .map_err(|cause| Error::encoding(cause, field))?;
622    end(&pair, field)?;
623    if role == OpenRole::SignedAssertion {
624        let saml = Oid::from_bytes(&[4, 0, 0x81, 0x95, 0x32, 3, 0]);
625        parent
626            .charge(1 + kind.as_bytes().len().min(saml.as_bytes().len()) as u64)
627            .map_err(|cause| Error::encoding(cause, field))?;
628        // EN319122-1 §5.2.6.3 types this registered payload as OCTET STRING;
629        // other assertion types stay open. Reading cannot authenticate SAML.
630        // https://www.etsi.org/deliver/etsi_en/319100_319199/31912201/01.03.01_60/en_31912201v010301p.pdf
631        if kind == saml
632            && let Some(part) = value
633        {
634            Octets::read(&pair, &part, field)?;
635        }
636    }
637    if role == OpenRole::PolicyQualifier
638        && let Some(part) = value
639    {
640        // EN319122-1 §5.2.9.2 and AnnexD declare qualifier OPTIONAL,
641        // including for registered OIDs. Only a present value has a type.
642        // https://www.etsi.org/deliver/etsi_en/319100_319199/31912201/01.03.01_60/en_31912201v010301p.pdf
643        let uri = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 9, 16, 5, 1]);
644        let notice = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 9, 16, 5, 2]);
645        let specification = Oid::from_bytes(&[4, 0, 0x81, 0x95, 0x32, 2, 1]);
646        parent
647            .charge(3 * (1 + kind.as_bytes().len() as u64))
648            .map_err(|cause| Error::encoding(cause, field))?;
649        if kind == uri || kind == notice || kind == specification {
650            if kind == uri {
651                uri_value(&pair, &part, field)?;
652            } else if kind == specification {
653                spec(&pair, &part, field)?;
654            } else if !structured_pkix::policy::signature_notice_understood(&pair, &part)
655                .map_err(Error::Pkix)?
656            {
657                return Err(syntax(field, part.span().start));
658            }
659        }
660    }
661    push(
662        open,
663        OpenValue {
664            role,
665            kind: Some(kind),
666            enclosure: *element,
667            value: value.map(|value| crate::source::Value::of(&pair, value, field)),
668        },
669        field,
670    )
671}
672
673fn push<'a, W: Work>(
674    out: &mut OwnedVec<OpenValue<'a>, W>,
675    value: OpenValue<'a>,
676    field: &'static str,
677) -> Result<()> {
678    let at = value.enclosure.span().start;
679    out.try_push(value)
680        .map_err(|cause| Error::resource(cause, field, at))
681}
682
683fn directory_string(parent: &Reader<'_>, value: &Element<'_>, field: &'static str) -> Result<()> {
684    if ![12, 19, 20, 28, 30]
685        .into_iter()
686        .any(|number| value.tag() == Tag::primitive(number))
687    {
688        return Err(syntax(field, value.span().start));
689    }
690    string(parent, value, field)
691}
692
693fn string<'a>(parent: &Reader<'a>, value: &Element<'a>, field: &'static str) -> Result<()> {
694    if value.contents().is_empty() {
695        return Err(syntax(field, value.span().start));
696    }
697    structured_pkix::der::conforms(parent, value, field).map_err(Error::Pkix)
698}
699
700fn spec<'a>(parent: &Reader<'a>, value: &Element<'a>, field: &'static str) -> Result<()> {
701    if value.tag() == Tag::primitive(universal::OID) {
702        parent
703            .oid(value)
704            .map_err(|cause| Error::encoding(cause, field))?;
705    } else {
706        uri_value(parent, value, field)?;
707    }
708    Ok(())
709}
710
711fn uri_value<'a>(parent: &Reader<'a>, value: &Element<'a>, field: &'static str) -> Result<()> {
712    let octets = Octets::read_ia5(parent, value, field)?;
713    let work = parent.execution();
714    let joined;
715    let bytes = if let Some(one) = octets.as_one() {
716        one
717    } else {
718        joined = octets.copy_in(parent, field, value.span().start)?;
719        &joined
720    };
721    work.charge_work(1 + bytes.len() as u64)
722        .map_err(|cause| Error::resource(cause, field, value.span().start))?;
723    let text = core::str::from_utf8(bytes).map_err(|_| syntax(field, value.span().start))?;
724    if !structured_pkix::general_name::uri_syntax(text, &work)
725        .map_err(|cause| Error::resource(cause, field, value.span().start))?
726    {
727        return Err(syntax(field, value.span().start));
728    }
729    Ok(())
730}
731
732fn sequence<'a>(
733    parent: &Reader<'a>,
734    value: &Element<'a>,
735    field: &'static str,
736) -> Result<Reader<'a>> {
737    require_tag(value, Tag::constructed(universal::SEQUENCE), field)?;
738    parent
739        .enter(value)
740        .map_err(|cause| Error::encoding(cause, field))
741}
742fn next<'a>(reader: &mut Reader<'a>, field: &'static str) -> Result<Element<'a>> {
743    reader
744        .next_element()
745        .map_err(|cause| Error::encoding(cause, field))?
746        .ok_or_else(|| syntax(field, reader.offset()))
747}
748fn end(reader: &Reader<'_>, field: &'static str) -> Result<()> {
749    if reader.is_empty() {
750        Ok(())
751    } else {
752        Err(syntax(field, reader.offset()))
753    }
754}
755fn require_tag(value: &Element<'_>, tag: Tag, field: &'static str) -> Result<()> {
756    if value.tag() == tag {
757        Ok(())
758    } else {
759        Err(syntax(field, value.span().start))
760    }
761}
762fn syntax(field: &'static str, at: u64) -> Error {
763    Error::Syntax {
764        code: ErrorCode::AttributeSyntax,
765        field,
766        at,
767    }
768}