Skip to main content

structured_cms/attribute_certificate/
controls.rs

1//! `AAControls` syntax and per-extension attribute restriction (RFC5755 §7.4).
2//!
3//! This does not establish an AA CA trust anchor or validate its certificate
4//! path. Callers apply every control from that trusted path, including to
5//! decrypted attributes and encAttrs. A readable control is not authority.
6//! <https://www.rfc-editor.org/rfc/rfc5755#section-7.4>
7
8use super::{end, sequence, tag};
9use crate::signer::required;
10use crate::{Error, ErrorCode, Result};
11use structured_asn1::memory::{OwnedVec, Work};
12use structured_asn1::{Element, Encoding, Integer, Oid, Parser, ScopedReader, Tag, universal};
13use structured_pkix::Extension;
14
15const FIELD: &str = "attributeCertificate.aaControls";
16/// `id-pe-aaControls`, 1.3.6.1.5.5.7.1.6.
17pub const IDENTIFIER: Oid<'static> = Oid::from_bytes(&[43, 6, 1, 5, 5, 7, 1, 6]);
18
19/// Complete original `AAControls`. Absence and an explicitly empty `AttrSpec` are
20/// preserved; neither grants an attribute independently of other path controls.
21#[derive(Debug)]
22pub struct AaControls<'a> {
23    parser: &'a Parser,
24    /// Original whole value SEQUENCE.
25    pub element: Element<'a>,
26    /// Original unbounded nonnegative path constraint; never truncated.
27    pub path_length: Option<Integer<'a>>,
28    /// Original pathLenConstraint encoding.
29    pub path_length_element: Option<Element<'a>>,
30    /// Original `[0] IMPLICIT AttrSpec`, absent independently of its contents.
31    pub permitted_element: Option<Element<'a>>,
32    /// Original permitted identifiers in sequence order, without deduplication.
33    pub permitted: OwnedVec<Oid<'a>>,
34    /// Original `[1] IMPLICIT AttrSpec`.
35    pub excluded_element: Option<Element<'a>>,
36    /// Original excluded identifiers, which override permitted entries.
37    pub excluded: OwnedVec<Oid<'a>>,
38    /// Original BOOLEAN, absent for the schema default TRUE.
39    pub unspecified_element: Option<Element<'a>>,
40    /// Effective permitUnSpecified, including its normative default.
41    pub permit_unspecified: bool,
42}
43
44impl<'a> AaControls<'a> {
45    /// Reads a complete original DER value with inherited source/current limits.
46    pub fn read(parent: &mut ScopedReader<'a, '_>) -> Result<Self> {
47        let element = required(parent, FIELD)?;
48        let result = Self::read_element(parent, &element)?;
49        end(parent, FIELD)?;
50        Ok(result)
51    }
52
53    /// Reads one original whole element, without a replacement Parser.
54    pub fn read_element(parent: &ScopedReader<'a, '_>, element: &Element<'a>) -> Result<Self> {
55        let mut der = parent
56            .reread(element, Encoding::Der)
57            .map_err(|e| Error::encoding(e, FIELD))?;
58        let element = required(&mut der, FIELD)?;
59        tag(&element, sequence(), FIELD)?;
60        end(&der, FIELD)?;
61        let mut inside = der.enter(&element).map_err(|e| Error::encoding(e, FIELD))?;
62        let mut next = inside
63            .next_element()
64            .map_err(|e| Error::encoding(e, FIELD))?;
65        let path_length_element = super::optional(
66            &mut inside,
67            &mut next,
68            Tag::primitive(universal::INTEGER),
69            FIELD,
70        )?;
71        let path_length = path_length_element
72            .map(|encoded| {
73                let number = inside
74                    .integer(&encoded)
75                    .map_err(|e| Error::encoding(e, FIELD))?;
76                if number.is_negative() {
77                    return Err(Error::schema(
78                        ErrorCode::MalformedValue,
79                        FIELD,
80                        encoded.span().start,
81                    ));
82                }
83                Ok(number)
84            })
85            .transpose()?;
86        let permitted_element =
87            super::optional(&mut inside, &mut next, Tag::context(true, 0), FIELD)?;
88        let excluded_element =
89            super::optional(&mut inside, &mut next, Tag::context(true, 1), FIELD)?;
90        let unspecified_element = super::optional(
91            &mut inside,
92            &mut next,
93            Tag::primitive(universal::BOOLEAN),
94            FIELD,
95        )?;
96        super::no_pending(next, FIELD)?;
97        let permit_unspecified = match unspecified_element {
98            Some(encoded) => {
99                let value = inside
100                    .boolean(&encoded)
101                    .map_err(|e| Error::encoding(e, FIELD))?;
102                // RFC5755 §7.4 + X.690 §11.5: TRUE is the DEFAULT and must
103                // be omitted in DER. Explicit FALSE is a distinct restriction.
104                if value {
105                    return Err(Error::schema(
106                        ErrorCode::DefaultWritten,
107                        FIELD,
108                        encoded.span().start,
109                    ));
110                }
111                false
112            }
113            None => true,
114        };
115        let permitted = identifiers(&inside, permitted_element)?;
116        let excluded = identifiers(&inside, excluded_element)?;
117        Ok(Self {
118            parser: parent.parser(),
119            element,
120            path_length,
121            path_length_element,
122            permitted_element,
123            permitted,
124            excluded_element,
125            excluded,
126            unspecified_element,
127            permit_unspecified,
128        })
129    }
130
131    /// Reads a registered PKC extension. Criticality MAY be TRUE (§7.4), so
132    /// either value is accepted; no PKC or trust assertion is fabricated.
133    pub fn read_extension<W: Work>(extension: &Extension<'a>, work: &W) -> Result<Self> {
134        let execution = work.execution_for(extension.parser());
135        execution
136            .charge_work(9)
137            .map_err(|e| Error::resource(e, FIELD, extension.span().start))?;
138        if extension.kind() != IDENTIFIER {
139            return Err(Error::schema(
140                ErrorCode::UnexpectedContentType,
141                FIELD,
142                extension.span().start,
143            ));
144        }
145        let mut reader = extension
146            .reader_scoped(&execution)
147            .map_err(|e| Error::encoding(e, FIELD))?;
148        Self::read(&mut reader)
149    }
150
151    /// This extension's attribute restriction only. Exclusions win even when
152    /// an OID also appears in permittedAttrs. Apply the intersection of all
153    /// required trusted-path controls; this predicate proves no path or trust.
154    pub fn allows<W: Work>(&self, kind: Oid<'_>, work: &W) -> Result<bool> {
155        let execution = work.execution_for(self.parser);
156        execution
157            .charge_work(1)
158            .map_err(|e| Error::resource(e, FIELD, self.element.span().start))?;
159        // §7.4 disallows excluded types for every issuer below the AA CA.
160        // A simultaneous permitted entry cannot override that prohibition.
161        // https://www.rfc-editor.org/rfc/rfc5755#section-7.4
162        for (list, answer) in [(&self.excluded, false), (&self.permitted, true)] {
163            for identifier in list {
164                let amount = identifier.as_bytes().len().min(kind.as_bytes().len()) as u64 + 1;
165                execution
166                    .charge_work(amount)
167                    .map_err(|e| Error::resource(e, FIELD, self.element.span().start))?;
168                if *identifier == kind {
169                    return Ok(answer);
170                }
171            }
172        }
173        Ok(self.permit_unspecified)
174    }
175
176    /// Compares a caller's already-established path distance to this one bound.
177    /// RFC5280 path-length counting and selecting the AA CA remain caller work.
178    /// Wide positive constraints exceed every u64 distance rather than wrapping.
179    pub fn permits_distance<W: Work>(&self, distance: u64, work: &W) -> Result<bool> {
180        let execution = work.execution_for(self.parser);
181        execution
182            .charge_work(10)
183            .map_err(|e| Error::resource(e, FIELD, self.element.span().start))?;
184        Ok(match self.path_length {
185            None => true,
186            Some(number) => match number.as_u64() {
187                Some(bound) => distance <= bound,
188                None => true,
189            },
190        })
191    }
192}
193
194fn identifiers<'a>(
195    parent: &ScopedReader<'a, '_>,
196    element: Option<Element<'a>>,
197) -> Result<OwnedVec<Oid<'a>>> {
198    let execution = parent.execution();
199    let mut result = execution.vector();
200    if let Some(encoded) = element {
201        let mut inside = parent
202            .enter(&encoded)
203            .map_err(|e| Error::encoding(e, FIELD))?;
204        while let Some(value) = inside
205            .next_element()
206            .map_err(|e| Error::encoding(e, FIELD))?
207        {
208            tag(&value, Tag::primitive(universal::OID), FIELD)?;
209            result
210                .try_reserve(1)
211                .map_err(|e| Error::resource(e, FIELD, value.span().start))?;
212            let identifier = inside.oid(&value).map_err(|e| Error::encoding(e, FIELD))?;
213            result
214                .try_push(identifier)
215                .map_err(|e| Error::resource(e, FIELD, value.span().start))?;
216        }
217    }
218    Ok(result.into_owned())
219}