Skip to main content

structured_pkix/
qualified.rs

1//! What a certificate claims about being a qualified certificate.
2//!
3//! These statements are claims the issuer makes, not facts this crate can
4//! confirm. Whether a certificate is qualified is decided by the trusted list,
5//! against the service that issued it, at the moment in question. A statement
6//! saying so is evidence about what the issuer intended, and the report has to
7//! keep the two apart.
8
9use crate::error::{Error, ErrorCode, Located, Result, gate};
10use crate::extension::{self, Extension};
11use crate::name::is_ia5;
12use crate::oid;
13use structured_asn1::memory::{Memory, OwnedVec, Work};
14use structured_asn1::{Oid, Parser, Tag, universal};
15
16/// One statement: what it is, and whatever was written with it.
17#[derive(Debug, Clone, Copy)]
18pub struct QcStatement<'a> {
19    kind: Oid<'a>,
20    info: Option<&'a [u8]>,
21    /// Where the information sits in the document, so an error found while
22    /// re-parsing it points at the statement rather than at the front of the
23    /// certificate.
24    at: u64,
25    /// Depth above the statement pair, retained for information decoders.
26    outer: u32,
27}
28
29impl<'a> QcStatement<'a> {
30    /// Which statement this is.
31    #[must_use]
32    pub fn kind(self) -> Oid<'a> {
33        self.kind
34    }
35
36    /// What was written with it, as encoded. What it means depends on the
37    /// statement, and the ones this crate reads are below.
38    #[must_use]
39    pub fn info(self) -> Option<&'a [u8]> {
40        self.info
41    }
42}
43
44/// How long the issuer says it keeps what is needed to check a certificate.
45///
46/// The statement is a bare `INTEGER` of years (ETSI TS 101 862, annex B), so
47/// a period wider than the counter is inside the schema. It is kept as what
48/// it is, longer than any interval a question will be asked over, rather
49/// than refused as malformed or folded into a number nobody wrote.
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum RetentionPeriod {
52    /// The number of years the statement names.
53    Years(u32),
54    /// A period past any interval a report covers.
55    BeyondAnyInterval,
56}
57
58impl RetentionPeriod {
59    /// The years where an interval could reach them, and `None` where none
60    /// can.
61    #[must_use]
62    pub fn years(self) -> Option<u32> {
63        match self {
64            Self::Years(years) => Some(years),
65            Self::BeyondAnyInterval => None,
66        }
67    }
68}
69
70/// Which kind of qualified certificate this claims to be.
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72#[non_exhaustive]
73pub enum QualifiedType {
74    /// For an electronic signature, which a natural person makes.
75    ElectronicSignature,
76    /// For an electronic seal, which a legal person makes.
77    ElectronicSeal,
78    /// For website authentication.
79    WebsiteAuthentication,
80}
81
82/// The statements a certificate carries.
83#[derive(Debug)]
84pub struct QcStatements<'a> {
85    statements: OwnedVec<QcStatement<'a>>,
86    parser: &'a Parser,
87}
88
89impl<'a> QcStatements<'a> {
90    /// Account retaining the statement collection's storage. Deferred
91    /// decoding uses the explicitly supplied current execution domain.
92    #[must_use]
93    pub fn memory(&self) -> &Memory {
94        self.statements.memory()
95    }
96
97    /// Re-read information at its original offset and depth under the same
98    /// document budget. Every lazy decoder goes through this boundary.
99    fn info_reader<'operation, W: Work>(
100        &self,
101        info: &'a [u8],
102        statement: &QcStatement<'a>,
103        execution: &'operation W,
104    ) -> structured_asn1::Result<structured_asn1::ScopedReader<'a, 'operation>>
105    where
106        'a: 'operation,
107    {
108        execution.read_scoped_nested(
109            self.parser,
110            info,
111            statement.at,
112            structured_asn1::Encoding::Der,
113            statement.outer,
114        )
115    }
116
117    /// Every statement, in order.
118    #[must_use]
119    pub fn statements(&self) -> &[QcStatement<'a>] {
120        &self.statements
121    }
122
123    /// One statement by identifier, for the ones this crate does not read.
124    pub fn get<W: Work>(&self, kind: Oid<'_>, execution: &W) -> Result<Option<QcStatement<'a>>> {
125        for statement in &self.statements {
126            let work = statement.kind.as_bytes().len().max(kind.as_bytes().len()) as u64;
127            execution
128                .charge_work(1 + work)
129                .map_err(|reason| Error::resource(reason, STATEMENT_FIELD, statement.at))?;
130            if statement.kind == kind {
131                return Ok(Some(*statement));
132            }
133        }
134        Ok(None)
135    }
136
137    /// Whether the issuer claims this is a qualified certificate.
138    ///
139    /// A claim, not a finding. The trusted list decides.
140    pub fn claims_compliance<W: Work>(&self, execution: &W) -> Result<bool> {
141        Ok(self.get(oid::QC_COMPLIANCE, execution)?.is_some())
142    }
143
144    /// Whether the issuer claims the private key is held in a qualified
145    /// device.
146    pub fn claims_qualified_device<W: Work>(&self, execution: &W) -> Result<bool> {
147        Ok(self.get(oid::QC_SSCD, execution)?.is_some())
148    }
149
150    /// Which kinds of qualified certificate this claims to be.
151    ///
152    /// This is what separates a signature made by a person from a seal made by
153    /// an organization, which is a difference the report must never collapse.
154    pub fn types<W: Work>(&self, execution: &W) -> Result<OwnedVec<QualifiedType>> {
155        const FIELD: &str = "extensions.qcStatements.qcType";
156        // Statement information lives inside the DER certificate like every
157        // extension value, so its re-parses below are DER whatever parser
158        // carries the budget.
159        let Some(statement) = self.get(oid::QC_TYPE, execution)? else {
160            return Ok(execution.memory().vector());
161        };
162        let info = statement
163            .info
164            .ok_or_else(|| Error::new(ErrorCode::MissingField, FIELD, statement.at))?;
165
166        // Re-parsed from where it sits, so every offset below is an offset in
167        // the document, which is what the error type promises.
168        let mut reader = self.info_reader(info, &statement, execution).field(FIELD)?;
169        let sequence = reader
170            .next_element()
171            .field(FIELD)?
172            .ok_or_else(|| Error::new(ErrorCode::MissingField, FIELD, statement.at))?;
173        if sequence.tag() != Tag::constructed(universal::SEQUENCE) {
174            return Err(Error::new(
175                ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
176                FIELD,
177                sequence.span().start,
178            ));
179        }
180        let mut inside = reader.enter(&sequence).field(FIELD)?;
181        let mut types = execution.vector();
182        while !inside.is_empty() {
183            let identifier = inside.read_oid().field(FIELD)?;
184            // An identifier this crate does not know is a kind defined
185            // elsewhere. It is skipped rather than refused, and stays visible
186            // through the statement it was written in.
187            if identifier == oid::QCT_ESIGN {
188                types
189                    .try_push(QualifiedType::ElectronicSignature)
190                    .map_err(|reason| Error::resource(reason, FIELD, inside.offset()))?;
191            } else if identifier == oid::QCT_ESEAL {
192                types
193                    .try_push(QualifiedType::ElectronicSeal)
194                    .map_err(|reason| Error::resource(reason, FIELD, inside.offset()))?;
195            } else if identifier == oid::QCT_WEB {
196                types
197                    .try_push(QualifiedType::WebsiteAuthentication)
198                    .map_err(|reason| Error::resource(reason, FIELD, inside.offset()))?;
199            }
200        }
201
202        Ok(types.into_owned())
203    }
204
205    /// How long the issuer says it keeps what is needed to check this
206    /// certificate, in years.
207    pub fn retention_period<W: Work>(&self, execution: &W) -> Result<Option<RetentionPeriod>> {
208        const FIELD: &str = "extensions.qcStatements.qcRetentionPeriod";
209        let Some(statement) = self.get(oid::QC_RETENTION_PERIOD, execution)? else {
210            return Ok(None);
211        };
212        let info = statement
213            .info
214            .ok_or_else(|| Error::new(ErrorCode::MissingField, FIELD, statement.at))?;
215
216        let mut reader = self.info_reader(info, &statement, execution).field(FIELD)?;
217        let years = reader.read_integer().field(FIELD)?;
218        if !reader.is_empty() {
219            return Err(Error::new(ErrorCode::TrailingData, FIELD, statement.at));
220        }
221
222        // Below zero is not a duration at all. Above the counter is still a
223        // duration, and what it says is that nothing asks over that long.
224        if years.is_negative() {
225            return Err(Error::new(ErrorCode::MalformedValue, FIELD, statement.at));
226        }
227        Ok(Some(years.as_u32().map_or(
228            RetentionPeriod::BeyondAnyInterval,
229            RetentionPeriod::Years,
230        )))
231    }
232
233    /// Which kind of person the subject's identifiers are to be read as.
234    ///
235    /// Says how to read an organization identifier or a serial number in the
236    /// subject's name, and getting it wrong turns an organization into a
237    /// person or the reverse.
238    pub fn semantics<W: Work>(&self, execution: &W) -> Result<Option<Oid<'a>>> {
239        // Both versions may be present, each saying which profile the
240        // certificate claims conformance with (RFC 3739 3.2.6), and each may
241        // carry an identifier. Two that differ say the subject's identifiers
242        // are to be read two ways, and the certificate does not say which:
243        // answering with either would put one of them in a report as though
244        // the document had settled it.
245        let mut said: Option<(Oid<'a>, u64)> = None;
246        for kind in [oid::PKIX_QC_SYNTAX_V2, oid::PKIX_QC_SYNTAX_V1] {
247            let Some(statement) = self.get(kind, execution)? else {
248                continue;
249            };
250            let Some(identifier) = self.semantics_parts(&statement, execution)?.0 else {
251                continue;
252            };
253            if said.is_some_and(|(said, _)| said != identifier) {
254                return Err(Error::new(
255                    ErrorCode::MalformedValue,
256                    "extensions.qcStatements.semanticsInformation",
257                    statement.at,
258                ));
259            }
260            said = Some((identifier, statement.at));
261        }
262        Ok(said.map(|(identifier, _)| identifier))
263    }
264
265    /// Reads one syntax statement's semantics information: the identifier if
266    /// one was written, and the registration authorities. The accessor above
267    /// prefers the newer statement, but where both appear each is validated on
268    /// its own: a well-formed preferred statement must not vouch for the other.
269    #[allow(clippy::type_complexity)]
270    fn semantics_parts<W: Work>(
271        &self,
272        statement: &QcStatement<'a>,
273        execution: &W,
274    ) -> Result<(
275        Option<Oid<'a>>,
276        OwnedVec<crate::general_name::GeneralName<'a>>,
277    )> {
278        const FIELD: &str = "extensions.qcStatements.semanticsInformation";
279        // The information is optional even where the statement is present.
280        let Some(info) = statement.info else {
281            return Ok((None, execution.memory().vector()));
282        };
283
284        let mut reader = self.info_reader(info, statement, execution).field(FIELD)?;
285        let sequence = reader
286            .next_element()
287            .field(FIELD)?
288            .ok_or_else(|| Error::new(ErrorCode::MissingField, FIELD, statement.at))?;
289        if sequence.tag() != Tag::constructed(universal::SEQUENCE) {
290            return Err(Error::new(
291                ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
292                FIELD,
293                sequence.span().start,
294            ));
295        }
296        let mut inside = reader.enter(&sequence).field(FIELD)?;
297        // Both members are optional, but at least one must be present
298        // (RFC 3739, 3.2.6.1): empty, the structure says nothing while
299        // wearing the statement.
300        let Some(first) = inside.next_element().field(FIELD)? else {
301            return Err(Error::new(ErrorCode::MissingField, FIELD, statement.at));
302        };
303
304        // Both members are optional, but the structure is closed: after the
305        // identifier only the list of registration authorities may follow,
306        // and anything else riding along under a value that decides person
307        // versus organization is a malformed statement, not extra context.
308        let parts = if first.tag() == Tag::primitive(universal::OID) {
309            let identifier = inside.oid(&first).field(FIELD)?;
310            match inside.next_element().field(FIELD)? {
311                Some(authorities) => (
312                    Some(identifier),
313                    require_authorities(&inside, &authorities)?,
314                ),
315                None => return Ok((Some(identifier), execution.memory().vector())),
316            }
317        } else {
318            (None, require_authorities(&inside, &first)?)
319        };
320
321        if !inside.is_empty() {
322            return Err(Error::new(ErrorCode::TrailingData, FIELD, statement.at));
323        }
324        Ok(parts)
325    }
326
327    /// Reads the value of a qualified statements extension.
328    pub fn read<W: Work>(extension: &Extension<'a>, execution: &W) -> Result<Self> {
329        const FIELD: &str = "extensions.qcStatements";
330
331        let mut inside = extension::sequence(extension, FIELD, execution)?;
332        let work = inside.execution_in(execution.memory());
333        let mut statements = work.vector();
334        let mut kinds = work.map::<Oid<'a>, Option<&'a [u8]>>();
335
336        while let Some(entry) = inside.next_element().field(FIELD)? {
337            let at = |code| Error::new(code, FIELD, entry.span().start);
338            if entry.tag() != Tag::constructed(universal::SEQUENCE) {
339                return Err(at(ErrorCode::Encoding(
340                    structured_asn1::ErrorCode::UnexpectedTag,
341                )));
342            }
343
344            let mut pair = inside.enter(&entry).field(FIELD)?;
345            let kind = pair.read_oid().field(FIELD)?;
346            let info_element = pair.next_element().field(FIELD)?;
347            let info = info_element.map(structured_asn1::Element::as_bytes);
348            let info_at =
349                info_element.map_or_else(|| entry.span().start, |element| element.span().start);
350            // Exactly one element. This check is what lets every decoder above
351            // re-parse the information knowing it holds one value and nothing
352            // after it: a statement written as a structure plus trailing bytes
353            // is refused here, not misread there.
354            if !pair.is_empty() {
355                return Err(at(ErrorCode::TrailingData));
356            }
357
358            // The payload is preserved for a scheme that knows the
359            // statement, and that scheme clears a critical extension on the
360            // strength of it, so the whole of it has to be the encoding the
361            // document claims and not only its outer tag.
362            if let Some(element) = info_element {
363                crate::name::walk_der(&pair, &element, FIELD, crate::name::Strings::Checked)?;
364            }
365
366            // One statement written twice with different information leaves
367            // no answer to what the issuer claimed. Written twice with the
368            // same information it claims one thing twice: the schema is a
369            // SEQUENCE OF with no uniqueness constraint, so the repeat is
370            // folded rather than refused. A map rather than a scan, so a
371            // long list costs the work its size says and not its square.
372            if let Some(existing) = kinds
373                .try_insert(kind, info)
374                .map_err(|reason| Error::resource(reason, FIELD, entry.span().start))?
375            {
376                // Deciding which of the two it is walks both payloads, which
377                // are opaque and have no local ceiling, so the longer of them
378                // is charged to the shared work budget first.
379                let compared = existing
380                    .map_or(0, <[u8]>::len)
381                    .max(info.map_or(0, <[u8]>::len));
382                inside.charge(compared as u64).field(FIELD)?;
383                if existing != info {
384                    return Err(at(ErrorCode::MalformedValue));
385                }
386                continue;
387            }
388
389            // The compliance and qualified-device statements are bare claims
390            // whose identifiers define no information value. A value there is
391            // malformed statement data, and a reader that kept the claim
392            // while carrying it would report a qualified certificate on the
393            // strength of a statement that is not one.
394            if info.is_some() && (kind == oid::QC_COMPLIANCE || kind == oid::QC_SSCD) {
395                return Err(at(ErrorCode::MalformedValue));
396            }
397
398            statements
399                .try_push(QcStatement {
400                    kind,
401                    info,
402                    at: info_at,
403                    outer: inside.depth(),
404                })
405                .map_err(|reason| Error::resource(reason, FIELD, entry.span().start))?;
406        }
407
408        // No minimum size: QCStatements is a bare SEQUENCE OF (RFC 3739
409        // 3.2.6), unlike the lists this crate refuses empty, whose schema
410        // writes SIZE (1..MAX). An empty one makes no qualified claim, which
411        // is an answer a report can give.
412        Ok(Self {
413            statements: statements.into_owned(),
414            parser: extension.parser(),
415        })
416    }
417}
418
419impl<'a> QcStatements<'a> {
420    /// Whether every statement was actually understood, for the gate that
421    /// decides what a critical extension blocked: each identifier is one
422    /// this crate names, and each structured payload has its shape. An error
423    /// is a spent budget and only that.
424    pub(crate) fn understood<E, W: Work>(
425        &self,
426        recognizer: &impl crate::extension::Recognizer<W, Error = E>,
427        execution: &W,
428    ) -> crate::error::CallbackResult<bool, E> {
429        if gate(self.types(execution))?.is_none()
430            || gate(self.retention_period(execution))?.is_none()
431        {
432            return Ok(false);
433        }
434        // Two syntax statements that name different semantics leave the
435        // subject's identifiers with two readings and the certificate with
436        // no answer between them. Each statement is well formed on its own,
437        // so this is asked once over the pair rather than inside the walk
438        // below.
439        if gate(self.semantics(execution))?.is_none() {
440            return Ok(false);
441        }
442        for statement in &self.statements {
443            let work = (statement.kind.as_bytes().len() as u64)
444                .checked_mul(8)
445                .and_then(|work| work.checked_add(1))
446                .ok_or_else(|| {
447                    Error::resource(
448                        structured_asn1::memory::MemoryError::WorkExceeded,
449                        STATEMENT_FIELD,
450                        statement.at,
451                    )
452                })?;
453            execution
454                .charge_work(work)
455                .map_err(|reason| Error::resource(reason, STATEMENT_FIELD, statement.at))?;
456            let kind = statement.kind;
457            let statement_understood = if kind == oid::QC_TYPE {
458                self.qc_types_understood(statement, execution)?
459            } else if kind == oid::QC_LIMIT_VALUE {
460                self.limit_value_understood(statement, execution)?
461            } else if kind == oid::QC_PDS {
462                self.pds_understood(statement, execution)?
463            } else if kind == oid::QC_CC_LEGISLATION {
464                self.legislation_understood(statement, execution)?
465            } else if kind == oid::PKIX_QC_SYNTAX_V1 || kind == oid::PKIX_QC_SYNTAX_V2 {
466                // Each syntax statement on its own, not just the one the
467                // accessor prefers: with both present, a well-formed
468                // preferred statement must not hide the other's garbage.
469                // An identifier decides how the subject's identifiers are
470                // read; one this crate does not name is a reading this run
471                // cannot perform. And the registration authorities answer
472                // the other-name question every list of names answers: a
473                // type nobody claims was never interpreted. The accessor
474                // still reports both.
475                match gate(self.semantics_parts(statement, execution))? {
476                    None => false,
477                    Some((identifier, authorities)) => {
478                        identifier.is_none_or(|identifier| {
479                            identifier == oid::SEMANTICS_NATURAL_PERSON
480                                || identifier == oid::SEMANTICS_LEGAL_PERSON
481                        }) && {
482                            let mut known = true;
483                            for name in &authorities {
484                                execution.charge_work(1).map_err(|reason| {
485                                    Error::resource(reason, "qcStatements", statement.at)
486                                })?;
487                                if !crate::extension::other_name_known(name, recognizer, execution)?
488                                {
489                                    known = false;
490                                    break;
491                                }
492                            }
493                            known
494                        }
495                    }
496                }
497            } else {
498                // A statement this crate does not name may still be one the
499                // caller's scheme carries: a national statement is the
500                // scheme's knowledge, and only with neither does a critical
501                // list hold a claim nobody read.
502                is_known_statement(kind)
503                    || recognizer
504                        .recognizes_statement(kind, execution)
505                        .map_err(crate::error::CallbackError::Callback)?
506            };
507            if !statement_understood {
508                return Ok(false);
509            }
510        }
511        Ok(true)
512    }
513
514    /// The type statement of ETSI EN 319 412-5, read for the gate: every
515    /// identifier is one of the three purposes this crate names.
516    ///
517    /// The accessor skips an unknown identifier so a report can still show
518    /// the rest; this gate cannot, because the payload's identifiers are what
519    /// carry the meaning, and a critical list declaring a purpose nobody here
520    /// can name was not understood (RFC 3739, 3.2.6: applications that do not
521    /// understand a statement in a critical extension reject the certificate).
522    fn qc_types_understood<W: Work>(
523        &self,
524        statement: &QcStatement<'a>,
525        execution: &W,
526    ) -> Result<bool> {
527        let Some(mut inside) = self.payload_sequence(statement, execution)? else {
528            return Ok(false);
529        };
530        // read_oid rather than the tag-agnostic decoding helper: the schema
531        // says SEQUENCE OF OBJECT IDENTIFIER, and this gate must not depend
532        // on another accessor happening to check the tag first.
533        let mut any = false;
534        while !inside.is_empty() {
535            let Some(identifier) = gate(inside.read_oid().field(STATEMENT_FIELD))? else {
536                return Ok(false);
537            };
538            if ![oid::QCT_ESIGN, oid::QCT_ESEAL, oid::QCT_WEB].contains(&identifier) {
539                return Ok(false);
540            }
541            any = true;
542        }
543        // The statement declares which purpose the certificate is issued for
544        // (ETSI EN 319 412-5, clause 4.2.3): zero purposes declares nothing
545        // while wearing the statement, which is not a statement understood.
546        Ok(any)
547    }
548
549    /// The monetary limit of ETSI EN 319 412-5: a currency as a three-letter
550    /// code or a number, then the amount and the exponent.
551    fn limit_value_understood<W: Work>(
552        &self,
553        statement: &QcStatement<'a>,
554        execution: &W,
555    ) -> Result<bool> {
556        let Some(mut inside) = self.payload_sequence(statement, execution)? else {
557            return Ok(false);
558        };
559        let Some(Some(currency)) = gate(inside.next_element().field(STATEMENT_FIELD))? else {
560            return Ok(false);
561        };
562        // The alphabetic choice is an ISO 4217 code, three uppercase
563        // letters; the numeric choice is INTEGER (1..999), that standard's
564        // numeric code space (ETSI EN 319 412-5, clause 4.3.2,
565        // Iso4217CurrencyCode). Either outside its form wears the type but
566        // names no currency. The universal tag is required first, because
567        // the decoding helper is deliberately tag-agnostic for implicitly
568        // tagged fields.
569        let currency_ok = (currency.tag() == Tag::primitive(universal::PRINTABLE_STRING)
570            && currency.contents().len() == 3
571            && currency.contents().iter().all(u8::is_ascii_uppercase))
572            || (currency.tag() == Tag::primitive(universal::INTEGER)
573                && gate(inside.integer(&currency).field(STATEMENT_FIELD))?.is_some_and(|code| {
574                    code.as_u32().is_some_and(|code| (1..=999).contains(&code))
575                }));
576        // The amount and the exponent are written `INTEGER`, with no range
577        // (ETSI EN 319 412-5, clause 4.3.2: `MonetaryValue ::= SEQUENCE {
578        // currency Iso4217CurrencyCode, amount INTEGER, exponent INTEGER }`,
579        // the currency being the only member the schema constrains). Neither
580        // is held to a sign here: a negative amount is inside the type, and
581        // refusing one would leave a critical extension unhandled over a
582        // value the schema names. What the issuer meant by it is the
583        // issuer's to answer, and this layer reports the claim rather than
584        // grading it.
585        Ok(currency_ok
586            && gate(inside.read_integer().field(STATEMENT_FIELD))?.is_some()
587            && gate(inside.read_integer().field(STATEMENT_FIELD))?.is_some()
588            && inside.is_empty())
589    }
590
591    /// The disclosure locations of ETSI EN 319 412-5: at least one pair of a
592    /// location and a two-letter language. Locations are read, never fetched.
593    fn pds_understood<W: Work>(&self, statement: &QcStatement<'a>, execution: &W) -> Result<bool> {
594        let Some(mut inside) = self.payload_sequence(statement, execution)? else {
595            return Ok(false);
596        };
597        let mut any = false;
598        loop {
599            match gate(inside.next_element().field(STATEMENT_FIELD))? {
600                Some(Some(location)) => {
601                    if location.tag() != Tag::constructed(universal::SEQUENCE) {
602                        return Ok(false);
603                    }
604                    let Some(mut pair) = gate(inside.enter(&location).field(STATEMENT_FIELD))?
605                    else {
606                        return Ok(false);
607                    };
608                    let Some(Some(url)) = gate(pair.next_element().field(STATEMENT_FIELD))? else {
609                        return Ok(false);
610                    };
611                    let Some(Some(language)) = gate(pair.next_element().field(STATEMENT_FIELD))?
612                    else {
613                        return Ok(false);
614                    };
615                    // The location is a URL, so it answers the same
616                    // absolute-URI question every location field answers;
617                    // the language is an ISO 639-1 code, two lowercase
618                    // letters, the form the pair's schema names. The URL
619                    // scans touch every content byte and the field has no
620                    // local size bound, so the length is charged to the
621                    // shared work budget first.
622                    pair.charge(url.contents().len() as u64)
623                        .field(STATEMENT_FIELD)?;
624                    if url.tag() != Tag::primitive(universal::IA5_STRING)
625                        || !is_ia5(url.contents())
626                        || !core::str::from_utf8(url.contents())
627                            .is_ok_and(crate::general_name::is_absolute_uri)
628                        || language.tag() != Tag::primitive(universal::PRINTABLE_STRING)
629                        || language.contents().len() != 2
630                        || !language.contents().iter().all(u8::is_ascii_lowercase)
631                        || !pair.is_empty()
632                    {
633                        return Ok(false);
634                    }
635                    any = true;
636                }
637                Some(None) => return Ok(any && inside.is_empty()),
638                None => return Ok(false),
639            }
640        }
641    }
642
643    /// The legislation countries of ETSI EN 319 412-5: at least one
644    /// two-letter country code.
645    fn legislation_understood<W: Work>(
646        &self,
647        statement: &QcStatement<'a>,
648        execution: &W,
649    ) -> Result<bool> {
650        let Some(mut inside) = self.payload_sequence(statement, execution)? else {
651            return Ok(false);
652        };
653        let mut any = false;
654        loop {
655            match gate(inside.next_element().field(STATEMENT_FIELD))? {
656                Some(Some(country)) => {
657                    // An ISO 3166-1 alpha-2 code, the form the entries are
658                    // defined as: two uppercase letters. A digit or
659                    // punctuation in the pair names no country.
660                    if country.tag() != Tag::primitive(universal::PRINTABLE_STRING)
661                        || country.contents().len() != 2
662                        || !country.contents().iter().all(u8::is_ascii_uppercase)
663                    {
664                        return Ok(false);
665                    }
666                    any = true;
667                }
668                Some(None) => return Ok(any),
669                None => return Ok(false),
670            }
671        }
672    }
673
674    /// The one sequence a structured payload holds, or `None` where the
675    /// information is absent or not that. An error is a spent budget.
676    fn payload_sequence<'operation, W: Work>(
677        &self,
678        statement: &QcStatement<'a>,
679        execution: &'operation W,
680    ) -> Result<Option<structured_asn1::ScopedReader<'a, 'operation>>>
681    where
682        'a: 'operation,
683    {
684        let Some(info) = statement.info else {
685            return Ok(None);
686        };
687        let Some(mut reader) = gate(
688            self.info_reader(info, statement, execution)
689                .field(STATEMENT_FIELD),
690        )?
691        else {
692            return Ok(None);
693        };
694        let Some(Some(sequence)) = gate(reader.next_element().field(STATEMENT_FIELD))? else {
695            return Ok(None);
696        };
697        if sequence.tag() != Tag::constructed(universal::SEQUENCE) || !reader.is_empty() {
698            return Ok(None);
699        }
700        gate(reader.enter(&sequence).field(STATEMENT_FIELD))
701    }
702}
703
704/// The field the statement gates above report a spent budget in.
705const STATEMENT_FIELD: &str = "extensions.qcStatements";
706
707/// Whether a qualified statement identifier is one this crate names.
708fn is_known_statement(kind: Oid<'_>) -> bool {
709    [
710        oid::QC_COMPLIANCE,
711        oid::QC_RETENTION_PERIOD,
712        oid::QC_SSCD,
713        oid::QC_TYPE,
714        oid::PKIX_QC_SYNTAX_V1,
715        oid::PKIX_QC_SYNTAX_V2,
716    ]
717    .contains(&kind)
718}
719
720/// Requires an element to be the list of registration authorities: a
721/// non-empty sequence of names. The names are validated as names, since a
722/// value that merely wears the sequence tag is the same malformed statement.
723fn require_authorities<'a>(
724    parent: &structured_asn1::ScopedReader<'a, '_>,
725    element: &structured_asn1::Element<'a>,
726) -> Result<OwnedVec<crate::general_name::GeneralName<'a>>> {
727    const FIELD: &str = "extensions.qcStatements.semanticsInformation";
728    if element.tag() != Tag::constructed(universal::SEQUENCE) {
729        return Err(Error::new(
730            ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
731            FIELD,
732            element.span().start,
733        ));
734    }
735    crate::general_name::read_all(parent, element, FIELD)
736}
737
738#[cfg(test)]
739mod tests;