Skip to main content

structured_cms/attribute_certificate/
clearance.rs

1//! Original clearance assertions (RFC5755 §4.4.6), without authorization policy.
2//!
3//! Both corrected and historical RFC3281 encodings can be inspected. No legacy
4//! encoding is emitted. Classification meaning and category syntax belong to
5//! the named security policy, not to this format reader.
6//! <https://www.rfc-editor.org/rfc/rfc5755#section-4.4.6>
7
8use super::{end, sequence, tag};
9use crate::attribute::Attribute;
10use crate::signer::required;
11use crate::{Error, ErrorCode, Result};
12use structured_asn1::memory::{OwnedVec, Work};
13use structured_asn1::{BitString, Element, Encoding, Oid, Parser, ScopedReader, Tag, universal};
14
15const FIELD: &str = "attributeCertificate.clearance";
16/// Current `id-at-clearance`, 2.5.4.55 (RFC5755 §4.4.6).
17pub const IDENTIFIER: Oid<'static> = Oid::from_bytes(&[85, 4, 55]);
18/// Historical registration, 2.5.1.5.55, readable but never emitted here.
19pub const LEGACY_IDENTIFIER: Oid<'static> = Oid::from_bytes(&[85, 1, 5, 55]);
20
21/// Which original field-tag convention was written.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum Syntax {
24    /// Corrected untagged fields, mandatory for the current registration.
25    Current,
26    /// Deprecated RFC3281 context-tagged fields.
27    Legacy,
28}
29
30/// An opaque, policy-specific security category, never a permission.
31#[derive(Debug, Clone, Copy)]
32pub struct SecurityCategory<'a> {
33    parser: &'a Parser,
34    depth: u32,
35    /// Original complete category SEQUENCE.
36    pub element: Element<'a>,
37    /// Validated original `[0] IMPLICIT OBJECT IDENTIFIER`.
38    pub kind: Oid<'a>,
39    /// Original identifier encoding.
40    pub kind_element: Element<'a>,
41    /// Original `[1] EXPLICIT` open-type wrapper.
42    pub wrapper: Element<'a>,
43    /// Exactly one original DER value. Its type-specific meaning is unknown.
44    pub value: Element<'a>,
45}
46
47impl<'a> SecurityCategory<'a> {
48    /// Opens the whole original policy-specific value, including its tag, at
49    /// its actual source depth under the current operation. The policy adapter
50    /// can interpret it without a fresh Parser or resetting either budget.
51    /// No temporary reader borrow is stored in the returned original facts.
52    pub fn value_reader<'operation, W: Work>(
53        &self,
54        work: &'operation W,
55    ) -> Result<ScopedReader<'a, 'operation>>
56    where
57        'a: 'operation,
58    {
59        work.read_scoped_source(
60            self.parser,
61            self.value.as_bytes(),
62            self.value.span().start,
63            Encoding::Der,
64            self.depth,
65        )
66        .map_err(|e| Error::encoding(e, FIELD))
67    }
68}
69
70/// Complete original clearance. Absence of classList means unclassified;
71/// absence of securityCategories differs from a present empty SET.
72#[derive(Debug)]
73pub struct Clearance<'a> {
74    /// Original whole SEQUENCE.
75    pub element: Element<'a>,
76    /// Original field-tag convention, not a policy choice.
77    pub syntax: Syntax,
78    /// Security policy identifier, not a trust assertion.
79    pub policy: Oid<'a>,
80    /// Original policy identifier field.
81    pub policy_element: Element<'a>,
82    /// Original named bits, including policy-defined positions beyond bit5.
83    pub classes: Option<BitString<'a>>,
84    /// Original classList field, absent for the schema default.
85    pub classes_element: Option<Element<'a>>,
86    /// Original categories SET, absent independently of its cardinality.
87    pub categories_element: Option<Element<'a>>,
88    /// Categories in original SET order, with retained capacity accounting.
89    pub categories: OwnedVec<SecurityCategory<'a>>,
90}
91
92impl<'a> Clearance<'a> {
93    /// Reads exactly one complete current or deprecated clearance syntax.
94    pub fn read(parent: &mut ScopedReader<'a, '_>) -> Result<Self> {
95        let element = required(parent, FIELD)?;
96        let result = Self::read_element(parent, &element)?;
97        end(parent, FIELD)?;
98        Ok(result)
99    }
100
101    /// Reads original DER fields without retagging or resetting source limits.
102    pub fn read_element(parent: &ScopedReader<'a, '_>, element: &Element<'a>) -> Result<Self> {
103        let mut der = parent
104            .reread(element, Encoding::Der)
105            .map_err(|e| Error::encoding(e, FIELD))?;
106        let element = required(&mut der, FIELD)?;
107        tag(&element, sequence(), FIELD)?;
108        end(&der, FIELD)?;
109        let mut inside = der.enter(&element).map_err(|e| Error::encoding(e, FIELD))?;
110        let policy_element = required(&mut inside, FIELD)?;
111        let syntax = if policy_element.tag() == Tag::context(false, 0) {
112            Syntax::Legacy
113        } else {
114            tag(&policy_element, Tag::primitive(universal::OID), FIELD)?;
115            Syntax::Current
116        };
117        let policy = inside
118            .oid(&policy_element)
119            .map_err(|e| Error::encoding(e, FIELD))?;
120        let class_tag = match syntax {
121            Syntax::Current => Tag::primitive(universal::BIT_STRING),
122            Syntax::Legacy => Tag::context(false, 1),
123        };
124        let set_tag = match syntax {
125            Syntax::Current => Tag::constructed(universal::SET),
126            Syntax::Legacy => Tag::context(true, 2),
127        };
128        let mut next = inside
129            .next_element()
130            .map_err(|e| Error::encoding(e, FIELD))?;
131        let classes_element = super::optional(&mut inside, &mut next, class_tag, FIELD)?;
132        let classes = classes_element
133            .map(|encoded| {
134                let bits = inside
135                    .bit_string(&encoded)
136                    .map_err(|e| Error::encoding(e, FIELD))?;
137                // X.690 §11.2.2: a named-bit list omits trailing zero bits. This
138                // does not impose a six-bit cap: RFC5755 allows policy extensions.
139                if !bits.is_empty() && !bits.bit(bits.len() - 1) {
140                    return Err(Error::schema(
141                        ErrorCode::MalformedValue,
142                        FIELD,
143                        encoded.span().start,
144                    ));
145                }
146                // X.690 §11.5 omits the complete DEFAULT {unclassified}, not every
147                // class list that happens to contain that bit.
148                if bits.len() == 2 && bits.as_bytes() == [0x40] {
149                    return Err(Error::schema(
150                        ErrorCode::DefaultWritten,
151                        FIELD,
152                        encoded.span().start,
153                    ));
154                }
155                Ok(bits)
156            })
157            .transpose()?;
158        let categories_element = next;
159        let execution = inside.execution();
160        let mut categories = execution.vector();
161        if let Some(set) = categories_element {
162            tag(&set, set_tag, FIELD)?;
163            let mut entries = inside.enter(&set).map_err(|e| Error::encoding(e, FIELD))?;
164            let mut previous = None;
165            while let Some(entry) = entries
166                .next_element()
167                .map_err(|e| Error::encoding(e, FIELD))?
168            {
169                if let Some(before) = previous
170                    && !structured_pkix::der::set_ordered(&entries, before, &entry, FIELD)
171                        .map_err(Error::pkix)?
172                {
173                    return Err(Error::schema(
174                        ErrorCode::SetOrder,
175                        FIELD,
176                        entry.span().start,
177                    ));
178                }
179                categories
180                    .try_reserve(1)
181                    .map_err(|e| Error::resource(e, FIELD, entry.span().start))?;
182                categories
183                    .try_push(category(&entries, &entry)?)
184                    .map_err(|e| Error::resource(e, FIELD, entry.span().start))?;
185                previous = Some(entry.as_bytes());
186            }
187        }
188        end(&inside, FIELD)?;
189        Ok(Self {
190            element,
191            syntax,
192            policy,
193            policy_element,
194            classes,
195            classes_element,
196            categories_element,
197            categories: categories.into_owned(),
198        })
199    }
200
201    /// Reads every registered value. Current OID forbids historical tagging;
202    /// historical OID supports both original and corrected syntax (§4.4.6).
203    pub fn read_attribute<W: Work>(attribute: &Attribute<'a>, work: &W) -> Result<OwnedVec<Self>> {
204        let execution = attribute.query_in(work);
205        execution
206            .charge_work(8)
207            .map_err(|e| Error::resource(e, FIELD, attribute.span().start))?;
208        let current = attribute.kind() == IDENTIFIER;
209        if !current && attribute.kind() != LEGACY_IDENTIFIER {
210            return Err(Error::schema(
211                ErrorCode::UnexpectedContentType,
212                FIELD,
213                attribute.span().start,
214            ));
215        }
216        if attribute.values().is_empty() {
217            return Err(Error::schema(
218                ErrorCode::AttributeCardinality,
219                FIELD,
220                attribute.span().start,
221            ));
222        }
223        let mut result = execution.vector();
224        result
225            .try_reserve_exact(attribute.values().len())
226            .map_err(|e| Error::resource(e, FIELD, attribute.span().start))?;
227        for index in 0..attribute.values().len() {
228            let mut reader = attribute
229                .value_reader_scoped(index, &execution, FIELD)?
230                .ok_or_else(|| {
231                    Error::schema(
232                        ErrorCode::AttributeCardinality,
233                        FIELD,
234                        attribute.span().start,
235                    )
236                })?;
237            let value = Self::read(&mut reader)?;
238            if current && value.syntax == Syntax::Legacy {
239                return Err(Error::schema(
240                    ErrorCode::AttributeSyntax,
241                    FIELD,
242                    value.policy_element.span().start,
243                ));
244            }
245            result
246                .try_push(value)
247                .map_err(|e| Error::resource(e, FIELD, reader.offset()))?;
248        }
249        Ok(result.into_owned())
250    }
251
252    /// Effective bit, including the unclassified default when classList is absent.
253    /// O(1) metadata access; no security-policy ranking is inferred.
254    #[must_use]
255    pub fn has_class(&self, position: u64) -> bool {
256        match self.classes {
257            Some(bits) => bits.bit(position),
258            None => position == 1,
259        }
260    }
261}
262
263fn category<'a>(
264    parent: &ScopedReader<'a, '_>,
265    element: &Element<'a>,
266) -> Result<SecurityCategory<'a>> {
267    tag(element, sequence(), FIELD)?;
268    let mut inside = parent
269        .enter(element)
270        .map_err(|e| Error::encoding(e, FIELD))?;
271    let kind_element = required(&mut inside, FIELD)?;
272    tag(&kind_element, Tag::context(false, 0), FIELD)?;
273    let kind = inside
274        .oid(&kind_element)
275        .map_err(|e| Error::encoding(e, FIELD))?;
276    let wrapper = required(&mut inside, FIELD)?;
277    tag(&wrapper, Tag::context(true, 1), FIELD)?;
278    end(&inside, FIELD)?;
279    let mut nested = inside
280        .enter(&wrapper)
281        .map_err(|e| Error::encoding(e, FIELD))?;
282    let value = required(&mut nested, FIELD)?;
283    end(&nested, FIELD)?;
284    structured_pkix::der::conforms(&nested, &value, FIELD).map_err(Error::pkix)?;
285    Ok(SecurityCategory {
286        parser: parent.parser(),
287        depth: nested.depth(),
288        element: *element,
289        kind,
290        kind_element,
291        wrapper,
292        value,
293    })
294}