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
209fn known(kind: Oid<'_>) -> Option<(Oid<'static>, &'static str)> {
210    for (expected, field) in [
211        (oid::COMMITMENT_TYPE_INDICATION, "commitmentTypeIndication"),
212        (oid::MIME_TYPE, "mimeType"),
213        (oid::CONTENT_HINTS, "contentHints"),
214        (oid::CONTENT_IDENTIFIER, "contentIdentifier"),
215        (oid::CONTENT_REFERENCE, "contentReference"),
216        (oid::SIGNER_LOCATION, "signerLocation"),
217        (oid::SIGNER_ATTRIBUTES_V2, "signerAttributesV2"),
218        (
219            oid::SIGNATURE_POLICY_IDENTIFIER,
220            "signaturePolicyIdentifier",
221        ),
222        (oid::SIGNATURE_POLICY_STORE, "signaturePolicyStore"),
223        (oid::SIGNATURE_TIMESTAMP, "signatureTimeStamp"),
224        (oid::COUNTERSIGNATURE, "countersignature"),
225    ] {
226        if kind == expected {
227            return Some((expected, field));
228        }
229    }
230    None
231}
232
233fn read_value<'a, W: Work>(
234    attribute: &Attribute<'a>,
235    kind: Oid<'static>,
236    field: &'static str,
237    parent: &Reader<'a>,
238    open: &mut OwnedVec<OpenValue<'a>, W>,
239    policy: &mut Option<Policy<'a>>,
240    store: &mut Option<Store<'a>>,
241) -> Result<()> {
242    if kind == oid::COUNTERSIGNATURE {
243        return if attribute.values().is_empty() {
244            Err(syntax(field, attribute.span().start))
245        } else {
246            Ok(())
247        };
248    }
249    if kind == oid::SIGNATURE_TIMESTAMP {
250        // EN319122-1 §6.3 Table1(l), not §5.3, requires DER for the WHOLE
251        // signature-time-stamp attribute. §5.3 only specifies its token type.
252        // This reports baseline conformance; generic CMS BER decoding and
253        // independent root/token signature verification remain available.
254        // https://www.etsi.org/deliver/etsi_en/319100_319199/31912201/01.03.01_60/en_31912201v010301p.pdf
255        let mut der = parent
256            .reread(attribute.element(), structured_asn1::Encoding::Der)
257            .map_err(|cause| Error::encoding(cause, field))?;
258        let element = der
259            .expect(Tag::constructed(universal::SEQUENCE))
260            .map_err(|cause| Error::encoding(cause, field))?;
261        return structured_pkix::der::conforms(&der, &element, field).map_err(Error::Pkix);
262    }
263    if attribute.values().len() != 1 {
264        return Err(syntax(field, attribute.span().start));
265    }
266    let mut reader = attribute
267        .value_reader_in(0, parent, field)?
268        .ok_or_else(|| syntax(field, attribute.span().start))?;
269    let value = reader
270        .next_element()
271        .map_err(|cause| Error::encoding(cause, field))?
272        .ok_or_else(|| syntax(field, reader.offset()))?;
273    if !reader.is_empty() {
274        return Err(syntax(field, reader.offset()));
275    }
276    if kind == oid::MIME_TYPE {
277        require_tag(&value, Tag::primitive(universal::UTF8_STRING), field)?;
278        structured_pkix::der::conforms(&reader, &value, field).map_err(Error::Pkix)?;
279        let valid =
280            structured_cms::mime::media_type_or_headers(value.contents(), &reader.execution())
281                .map_err(|cause| Error::resource(cause, field, value.span().start))?;
282        return if valid {
283            Ok(())
284        } else {
285            Err(syntax(field, value.span().start))
286        };
287    }
288    if kind == oid::CONTENT_IDENTIFIER {
289        Octets::read(&reader, &value, field)?;
290        return Ok(());
291    }
292    let inside = sequence(&reader, &value, field)?;
293    read_sequence(inside, kind, field, open, policy, store)
294}
295
296fn read_sequence<'a, W: Work>(
297    mut inside: Reader<'a>,
298    kind: Oid<'static>,
299    field: &'static str,
300    open: &mut OwnedVec<OpenValue<'a>, W>,
301    policy: &mut Option<Policy<'a>>,
302    store: &mut Option<Store<'a>>,
303) -> Result<()> {
304    if kind == oid::CONTENT_HINTS {
305        let first = next(&mut inside, field)?;
306        if first.tag() == Tag::primitive(universal::UTF8_STRING) {
307            string(&inside, &first, field)?;
308            inside
309                .read_oid()
310                .map_err(|cause| Error::encoding(cause, field))?;
311        } else {
312            inside
313                .oid(&first)
314                .map_err(|cause| Error::encoding(cause, field))?;
315        }
316    } else if kind == oid::CONTENT_REFERENCE {
317        inside
318            .read_oid()
319            .map_err(|cause| Error::encoding(cause, field))?;
320        for _ in 0..2 {
321            let part = next(&mut inside, field)?;
322            Octets::read(&inside, &part, field)?;
323        }
324    } else if kind == oid::COMMITMENT_TYPE_INDICATION {
325        inside
326            .read_oid()
327            .map_err(|cause| Error::encoding(cause, field))?;
328        if let Some(qualifiers) = inside
329            .next_element()
330            .map_err(|cause| Error::encoding(cause, field))?
331        {
332            let mut values = sequence(&inside, &qualifiers, field)?;
333            if values.is_empty() {
334                return Err(syntax(field, qualifiers.span().start));
335            }
336            while let Some(value) = values
337                .next_element()
338                .map_err(|cause| Error::encoding(cause, field))?
339            {
340                open_pair(&values, &value, OpenRole::CommitmentQualifier, field, open)?;
341            }
342        }
343    } else if kind == oid::SIGNER_LOCATION {
344        location(&mut inside, field)?;
345    } else if kind == oid::SIGNER_ATTRIBUTES_V2 {
346        attributes(&mut inside, field, open)?;
347    } else if kind == oid::SIGNATURE_POLICY_IDENTIFIER {
348        let identifier = inside
349            .read_oid()
350            .map_err(|cause| Error::encoding(cause, field))?;
351        let hash_value = next(&mut inside, field)?;
352        let mut hash_fields = sequence(&inside, &hash_value, field)?;
353        let id = next(&mut hash_fields, field)?;
354        let algorithm = algorithm::read(&hash_fields, &id, field).map_err(Error::Pkix)?;
355        let hash_value = next(&mut hash_fields, field)?;
356        let hash = Octets::read(&hash_fields, &hash_value, field)?;
357        end(&hash_fields, field)?;
358        let mut hash_unknown = true;
359        for piece in hash.pieces() {
360            inside
361                .charge(1 + piece.as_bytes().len() as u64)
362                .map_err(|cause| Error::encoding(cause, field))?;
363            if piece.as_bytes().iter().any(|byte| *byte != 0) {
364                hash_unknown = false;
365            }
366        }
367        if let Some(qualifiers) = inside
368            .next_element()
369            .map_err(|cause| Error::encoding(cause, field))?
370        {
371            let mut values = sequence(&inside, &qualifiers, field)?;
372            if values.is_empty() {
373                return Err(syntax(field, qualifiers.span().start));
374            }
375            while let Some(value) = values
376                .next_element()
377                .map_err(|cause| Error::encoding(cause, field))?
378            {
379                open_pair(&values, &value, OpenRole::PolicyQualifier, field, open)?;
380            }
381        }
382        *policy = Some(Policy {
383            identifier,
384            algorithm,
385            hash,
386            hash_unknown,
387        });
388    } else if kind == oid::SIGNATURE_POLICY_STORE {
389        let specification = next(&mut inside, field)?;
390        spec(&inside, &specification, field)?;
391        let document = next(&mut inside, field)?;
392        if document.tag().number() == Some(universal::OCTET_STRING) {
393            Octets::read(&inside, &document, field)?;
394        } else {
395            // EN319122-1 §5.2.10 selects a local-store URI, not arbitrary
396            // IA5 text. Syntax validation never fetches or resolves the URI.
397            // https://www.etsi.org/deliver/etsi_en/319100_319199/31912201/01.03.01_60/en_31912201v010301p.pdf
398            uri_value(&inside, &document, field)?;
399        }
400        *store = Some(Store {
401            specification,
402            document: crate::source::Value::of(&inside, document, field),
403        });
404    }
405    end(&inside, field)
406}
407
408fn location(reader: &mut Reader<'_>, field: &'static str) -> Result<()> {
409    // Annex D uses EXPLICIT TAGS, even though the containing CMS is BER.
410    // At least one field and postalAddress SIZE(1..6) are format constraints.
411    let mut previous = None;
412    while let Some(value) = reader
413        .next_element()
414        .map_err(|cause| Error::encoding(cause, field))?
415    {
416        let Some(number) = value.tag().number() else {
417            return Err(syntax(field, value.span().start));
418        };
419        if number > 2
420            || value.tag() != Tag::context(true, number)
421            || previous.is_some_and(|p| number <= p)
422        {
423            return Err(syntax(field, value.span().start));
424        }
425        previous = Some(number);
426        let mut explicit = reader
427            .enter(&value)
428            .map_err(|cause| Error::encoding(cause, field))?;
429        let part = next(&mut explicit, field)?;
430        end(&explicit, field)?;
431        if number < 2 {
432            directory_string(&explicit, &part, field)?;
433        } else {
434            let mut lines = sequence(&explicit, &part, field)?;
435            let mut count = 0;
436            while let Some(line) = lines
437                .next_element()
438                .map_err(|cause| Error::encoding(cause, field))?
439            {
440                count += 1;
441                if count > 6 {
442                    return Err(syntax(field, line.span().start));
443                }
444                directory_string(&lines, &line, field)?;
445            }
446            if count == 0 {
447                return Err(syntax(field, part.span().start));
448            }
449        }
450    }
451    if previous.is_none() {
452        return Err(syntax(field, reader.offset()));
453    }
454    Ok(())
455}
456
457fn claimed_value(claimed: &Attribute<'_>, parent: &Reader<'_>, field: &'static str) -> Result<()> {
458    parent
459        .charge(
460            1 + claimed
461                .kind()
462                .as_bytes()
463                .len()
464                .min(oid::CLAIMED_SAML_ASSERTION.as_bytes().len()) as u64,
465        )
466        .map_err(|cause| Error::encoding(cause, field))?;
467    if claimed.kind() != oid::CLAIMED_SAML_ASSERTION {
468        return Ok(());
469    }
470    // EN319122-1 §5.2.6.2 requires exactly one OCTET STRING. Reading
471    // this envelope neither interprets nor authenticates the SAML claim.
472    // https://www.etsi.org/deliver/etsi_en/319100_319199/31912201/01.03.01_60/en_31912201v010301p.pdf
473    claimed.single(field)?;
474    let mut reader = claimed
475        .value_reader_in(0, parent, field)?
476        .ok_or_else(|| syntax(field, claimed.span().start))?;
477    let value = next(&mut reader, field)?;
478    Octets::read(&reader, &value, field)?;
479    Ok(())
480}
481
482fn attributes<'a, W: Work>(
483    reader: &mut Reader<'a>,
484    field: &'static str,
485    open: &mut OwnedVec<OpenValue<'a>, W>,
486) -> Result<()> {
487    let mut previous = None;
488    let mut populated = false;
489    while let Some(value) = reader
490        .next_element()
491        .map_err(|cause| Error::encoding(cause, field))?
492    {
493        let Some(number) = value.tag().number() else {
494            return Err(syntax(field, value.span().start));
495        };
496        if number > 2
497            || value.tag() != Tag::context(true, number)
498            || previous.is_some_and(|p| number <= p)
499        {
500            return Err(syntax(field, value.span().start));
501        }
502        previous = Some(number);
503        let mut explicit = reader
504            .enter(&value)
505            .map_err(|cause| Error::encoding(cause, field))?;
506        let part = next(&mut explicit, field)?;
507        end(&explicit, field)?;
508        let mut entries = sequence(&explicit, &part, field)?;
509        // §5.2.6.1 requires nonempty certified and assertion sequences.
510        if number != 0 && entries.is_empty() {
511            return Err(syntax(field, part.span().start));
512        }
513        while let Some(entry) = entries
514            .next_element()
515            .map_err(|cause| Error::encoding(cause, field))?
516        {
517            populated = true;
518            if number == 0 {
519                let claimed = Attribute::read(&entries, &entry, field)?;
520                claimed_value(&claimed, &entries, field)?;
521                push(
522                    open,
523                    OpenValue {
524                        role: OpenRole::Claimed,
525                        kind: Some(claimed.kind()),
526                        enclosure: entry,
527                        value: Some(crate::source::Value::of(&entries, entry, field)),
528                    },
529                    field,
530                )?;
531            } else if number == 1 {
532                let choice = entry.tag();
533                if choice != Tag::context(true, 0) && choice != Tag::context(true, 1) {
534                    return Err(syntax(field, entry.span().start));
535                }
536                let mut explicit = entries
537                    .enter(&entry)
538                    .map_err(|cause| Error::encoding(cause, field))?;
539                let certificate = next(&mut explicit, field)?;
540                end(&explicit, field)?;
541                if choice.number() == Some(0) {
542                    require_tag(&certificate, Tag::constructed(universal::SEQUENCE), field)?;
543                    push(
544                        open,
545                        OpenValue {
546                            role: OpenRole::AttributeCertificate,
547                            kind: None,
548                            enclosure: certificate,
549                            value: Some(crate::source::Value::of(&explicit, certificate, field)),
550                        },
551                        field,
552                    )?;
553                } else {
554                    open_pair(
555                        &explicit,
556                        &certificate,
557                        OpenRole::OtherAttributeCertificate,
558                        field,
559                        open,
560                    )?;
561                }
562            } else {
563                open_pair(&entries, &entry, OpenRole::SignedAssertion, field, open)?;
564            }
565        }
566    }
567    // EN319122-1 §5.2.6.1 forbids an empty signer-attributes-v2, not
568    // merely an absent wrapper. An empty claimedAttributes is allowed only
569    // when another field actually contributes an entry.
570    // https://www.etsi.org/deliver/etsi_en/319100_319199/31912201/01.03.01_60/en_31912201v010301p.pdf
571    if !populated {
572        return Err(syntax(field, reader.offset()));
573    }
574    Ok(())
575}
576
577fn open_pair<'a, W: Work>(
578    parent: &Reader<'a>,
579    element: &Element<'a>,
580    role: OpenRole,
581    field: &'static str,
582    open: &mut OwnedVec<OpenValue<'a>, W>,
583) -> Result<()> {
584    let mut pair = sequence(parent, element, field)?;
585    let kind = pair
586        .read_oid()
587        .map_err(|cause| Error::encoding(cause, field))?;
588    let value = pair
589        .next_element()
590        .map_err(|cause| Error::encoding(cause, field))?;
591    end(&pair, field)?;
592    if role == OpenRole::SignedAssertion {
593        let saml = Oid::from_bytes(&[4, 0, 0x81, 0x95, 0x32, 3, 0]);
594        parent
595            .charge(1 + kind.as_bytes().len().min(saml.as_bytes().len()) as u64)
596            .map_err(|cause| Error::encoding(cause, field))?;
597        // EN319122-1 §5.2.6.3 types this registered payload as OCTET STRING;
598        // other assertion types stay open. Reading cannot authenticate SAML.
599        // https://www.etsi.org/deliver/etsi_en/319100_319199/31912201/01.03.01_60/en_31912201v010301p.pdf
600        if kind == saml
601            && let Some(part) = value
602        {
603            Octets::read(&pair, &part, field)?;
604        }
605    }
606    if role == OpenRole::PolicyQualifier
607        && let Some(part) = value
608    {
609        // EN319122-1 §5.2.9.2 and AnnexD declare qualifier OPTIONAL,
610        // including for registered OIDs. Only a present value has a type.
611        // https://www.etsi.org/deliver/etsi_en/319100_319199/31912201/01.03.01_60/en_31912201v010301p.pdf
612        let uri = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 9, 16, 5, 1]);
613        let notice = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 9, 16, 5, 2]);
614        let specification = Oid::from_bytes(&[4, 0, 0x81, 0x95, 0x32, 2, 1]);
615        parent
616            .charge(3 * (1 + kind.as_bytes().len() as u64))
617            .map_err(|cause| Error::encoding(cause, field))?;
618        if kind == uri || kind == notice || kind == specification {
619            if kind == uri {
620                uri_value(&pair, &part, field)?;
621            } else if kind == specification {
622                spec(&pair, &part, field)?;
623            } else if !structured_pkix::policy::signature_notice_understood(&pair, &part)
624                .map_err(Error::Pkix)?
625            {
626                return Err(syntax(field, part.span().start));
627            }
628        }
629    }
630    push(
631        open,
632        OpenValue {
633            role,
634            kind: Some(kind),
635            enclosure: *element,
636            value: value.map(|value| crate::source::Value::of(&pair, value, field)),
637        },
638        field,
639    )
640}
641
642fn push<'a, W: Work>(
643    out: &mut OwnedVec<OpenValue<'a>, W>,
644    value: OpenValue<'a>,
645    field: &'static str,
646) -> Result<()> {
647    let at = value.enclosure.span().start;
648    out.try_push(value)
649        .map_err(|cause| Error::resource(cause, field, at))
650}
651
652fn directory_string(parent: &Reader<'_>, value: &Element<'_>, field: &'static str) -> Result<()> {
653    if ![12, 19, 20, 28, 30]
654        .into_iter()
655        .any(|number| value.tag() == Tag::primitive(number))
656    {
657        return Err(syntax(field, value.span().start));
658    }
659    string(parent, value, field)
660}
661
662fn string<'a>(parent: &Reader<'a>, value: &Element<'a>, field: &'static str) -> Result<()> {
663    if value.contents().is_empty() {
664        return Err(syntax(field, value.span().start));
665    }
666    structured_pkix::der::conforms(parent, value, field).map_err(Error::Pkix)
667}
668
669fn spec<'a>(parent: &Reader<'a>, value: &Element<'a>, field: &'static str) -> Result<()> {
670    if value.tag() == Tag::primitive(universal::OID) {
671        parent
672            .oid(value)
673            .map_err(|cause| Error::encoding(cause, field))?;
674    } else {
675        uri_value(parent, value, field)?;
676    }
677    Ok(())
678}
679
680fn uri_value<'a>(parent: &Reader<'a>, value: &Element<'a>, field: &'static str) -> Result<()> {
681    let octets = Octets::read_ia5(parent, value, field)?;
682    let work = parent.execution();
683    let joined;
684    let bytes = if let Some(one) = octets.as_one() {
685        one
686    } else {
687        joined = octets.copy_in(parent, field, value.span().start)?;
688        &joined
689    };
690    work.charge_work(1 + bytes.len() as u64)
691        .map_err(|cause| Error::resource(cause, field, value.span().start))?;
692    let text = core::str::from_utf8(bytes).map_err(|_| syntax(field, value.span().start))?;
693    if !structured_pkix::general_name::uri_syntax(text, &work)
694        .map_err(|cause| Error::resource(cause, field, value.span().start))?
695    {
696        return Err(syntax(field, value.span().start));
697    }
698    Ok(())
699}
700
701fn sequence<'a>(
702    parent: &Reader<'a>,
703    value: &Element<'a>,
704    field: &'static str,
705) -> Result<Reader<'a>> {
706    require_tag(value, Tag::constructed(universal::SEQUENCE), field)?;
707    parent
708        .enter(value)
709        .map_err(|cause| Error::encoding(cause, field))
710}
711fn next<'a>(reader: &mut Reader<'a>, field: &'static str) -> Result<Element<'a>> {
712    reader
713        .next_element()
714        .map_err(|cause| Error::encoding(cause, field))?
715        .ok_or_else(|| syntax(field, reader.offset()))
716}
717fn end(reader: &Reader<'_>, field: &'static str) -> Result<()> {
718    if reader.is_empty() {
719        Ok(())
720    } else {
721        Err(syntax(field, reader.offset()))
722    }
723}
724fn require_tag(value: &Element<'_>, tag: Tag, field: &'static str) -> Result<()> {
725    if value.tag() == tag {
726        Ok(())
727    } else {
728        Err(syntax(field, value.span().start))
729    }
730}
731fn syntax(field: &'static str, at: u64) -> Error {
732    Error::Syntax {
733        code: ErrorCode::AttributeSyntax,
734        field,
735        at,
736    }
737}