Skip to main content

structured_cms/attribute_certificate/
ietf.rs

1//! Original IETF group/charging attribute values (RFC5755 §4.4).
2//!
3//! These are assertions, not authenticated memberships or authorization facts.
4//! A policy authority is distinct from the AC issuer, and is not discovered or
5//! trusted by decoding its name. No value is normalized or re-encoded.
6//! <https://www.rfc-editor.org/rfc/rfc5755#section-4.4>
7
8use super::{end, names, sequence, tag};
9use crate::attribute::Attribute;
10use crate::signer::required;
11use crate::{Error, ErrorCode, Result};
12use structured_asn1::memory::{OwnedVec, Work};
13use structured_asn1::{Element, Encoding, Oid, ScopedReader, Tag, universal};
14use structured_pkix::general_name::GeneralName;
15
16const FIELD: &str = "attributeCertificate.ietfAttrSyntax";
17
18/// `id-aca-chargingIdentity`, RFC5755 §4.4.3 / Appendix B.
19pub const CHARGING_IDENTITY: Oid<'static> = Oid::from_bytes(&[43, 6, 1, 5, 5, 7, 10, 3]);
20/// `id-aca-group`, RFC5755 §4.4.4 / Appendix B.
21pub const GROUP: Oid<'static> = Oid::from_bytes(&[43, 6, 1, 5, 5, 7, 10, 4]);
22
23/// The syntax selected by every member of the values sequence.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum Choice {
26    /// Primitive DER OCTET STRING.
27    Octets,
28    /// ASN.1 OBJECT IDENTIFIER, validated by the common reader.
29    Oid,
30    /// Primitive DER `UTF8String`, without text normalization.
31    Utf8,
32}
33
34/// One borrowed, validated value; no allocating or lossy text projection.
35#[derive(Debug, Clone, Copy)]
36pub enum Value<'a> {
37    /// Original octet contents, including empty octets.
38    Octets(&'a [u8]),
39    /// Original identifier octets, without a decimal string conversion.
40    Oid(Oid<'a>),
41    /// Original UTF-8 text, including empty text.
42    Utf8(&'a str),
43}
44impl Value<'_> {
45    /// This member's syntax. All members of a reading have the same choice.
46    #[must_use]
47    pub fn choice(self) -> Choice {
48        match self {
49            Self::Octets(_) => Choice::Octets,
50            Self::Oid(_) => Choice::Oid,
51            Self::Utf8(_) => Choice::Utf8,
52        }
53    }
54}
55
56/// A typed value and its entire original encoding/location.
57#[derive(Debug, Clone, Copy)]
58pub struct Member<'a> {
59    /// Original whole member, without stripping its source range.
60    pub element: Element<'a>,
61    /// Borrowed validated contents.
62    pub value: Value<'a>,
63}
64
65/// Fully decoded `IetfAttrSyntax` with independently retained metadata storage.
66/// The original document must remain alive; a shorter query need not.
67#[derive(Debug)]
68pub struct IetfAttrSyntax<'a> {
69    element: Element<'a>,
70    authority: Option<OwnedVec<GeneralName<'a>>>,
71    authority_element: Option<Element<'a>>,
72    values: OwnedVec<Member<'a>>,
73    choice: Option<Choice>,
74}
75
76impl<'a> IetfAttrSyntax<'a> {
77    /// Reads one complete standalone DER value. A BER envelope does not relax
78    /// the DER requirement of AC signed information (RFC5755 §4.1).
79    pub fn read(parent: &mut ScopedReader<'a, '_>) -> Result<Self> {
80        let element = required(parent, FIELD)?;
81        let reading = Self::read_element(parent, &element)?;
82        end(parent, FIELD)?;
83        Ok(reading)
84    }
85
86    /// Decodes a whole original element in its producing context. Root headers
87    /// and contents follow DER, preserving source offsets, nesting and current
88    /// execution. No replacement Parser or retagged bytes are manufactured.
89    pub fn read_element(parent: &ScopedReader<'a, '_>, element: &Element<'a>) -> Result<Self> {
90        let mut der = parent
91            .reread(element, Encoding::Der)
92            .map_err(|e| Error::encoding(e, FIELD))?;
93        let element = required(&mut der, FIELD)?;
94        tag(&element, sequence(), FIELD)?;
95        end(&der, FIELD)?;
96        let mut inside = der.enter(&element).map_err(|e| Error::encoding(e, FIELD))?;
97        let mut first = required(&mut inside, FIELD)?;
98        let (authority, authority_element) = if first.tag() == Tag::context(true, 0) {
99            // RFC5755 Appendix B uses IMPLICIT TAGS. GeneralNames is a
100            // SEQUENCE, so [0] replaces its tag; no explicit inner SEQUENCE.
101            let authority = names(&inside, &first, FIELD)?;
102            let encoded = first;
103            first = required(&mut inside, FIELD)?;
104            (Some(authority), Some(encoded))
105        } else {
106            (None, None)
107        };
108        tag(&first, sequence(), FIELD)?;
109        end(&inside, FIELD)?;
110        let mut members = inside
111            .enter(&first)
112            .map_err(|e| Error::encoding(e, FIELD))?;
113        let execution = inside.execution();
114        let mut values = execution.vector();
115        let mut choice = None;
116        while let Some(encoded) = members
117            .next_element()
118            .map_err(|e| Error::encoding(e, FIELD))?
119        {
120            let selected = match encoded.tag() {
121                t if t == Tag::primitive(universal::OCTET_STRING) => Choice::Octets,
122                t if t == Tag::primitive(universal::OID) => Choice::Oid,
123                t if t == Tag::primitive(universal::UTF8_STRING) => Choice::Utf8,
124                _ => {
125                    return Err(Error::schema(
126                        ErrorCode::UnexpectedTag,
127                        FIELD,
128                        encoded.span().start,
129                    ));
130                }
131            };
132            // RFC5755 §4.4 requires one syntax across ALL values. Do not
133            // accept a valid prefix and silently discard a later mixed choice.
134            if let Some(previous) = choice
135                && previous != selected
136            {
137                return Err(Error::schema(
138                    ErrorCode::AttributeSyntax,
139                    FIELD,
140                    encoded.span().start,
141                ));
142            }
143            let value = match selected {
144                Choice::Octets => Value::Octets(encoded.contents()),
145                Choice::Oid => Value::Oid(
146                    members
147                        .oid(&encoded)
148                        .map_err(|e| Error::encoding(e, FIELD))?,
149                ),
150                Choice::Utf8 => {
151                    members
152                        .charge(encoded.contents().len() as u64)
153                        .map_err(|e| Error::encoding(e, FIELD))?;
154                    Value::Utf8(core::str::from_utf8(encoded.contents()).map_err(|e| {
155                        Error::schema(
156                            ErrorCode::MalformedValue,
157                            FIELD,
158                            encoded.contents_span().start + e.valid_up_to() as u64,
159                        )
160                    })?)
161                }
162            };
163            values
164                .try_push(Member {
165                    element: encoded,
166                    value,
167                })
168                .map_err(|e| Error::resource(e, FIELD, encoded.span().start))?;
169            choice = Some(selected);
170        }
171        // The values SEQUENCE OF in §4.4 has no SIZE constraint. GeneralNames
172        // itself is nonempty, but an empty values sequence has no chosen type.
173        Ok(Self {
174            element,
175            authority,
176            authority_element,
177            values: values.into_owned(),
178            choice,
179        })
180    }
181
182    /// Reads a registered group or chargingIdentity Attribute. RFC5755
183    /// §§4.4.3–4.4.4 permit exactly one outer `AttributeValue`, with multiplicity
184    /// inside `IetfAttrSyntax`. OID/cardinality checks precede value decoding.
185    /// This does not infer group membership, authority trust or charging policy.
186    pub fn read_attribute<W: Work>(attribute: &Attribute<'a>, work: &W) -> Result<Self> {
187        let execution = attribute.query_in(work);
188        // Two fixed-width identifier comparisons plus cardinality metadata;
189        // an attacker-selected longer OID is rejected by length, not scanned.
190        execution
191            .charge_work(17)
192            .map_err(|e| Error::resource(e, FIELD, attribute.span().start))?;
193        if attribute.kind() != GROUP && attribute.kind() != CHARGING_IDENTITY {
194            return Err(Error::schema(
195                ErrorCode::UnexpectedContentType,
196                FIELD,
197                attribute.span().start,
198            ));
199        }
200        if attribute.values().len() != 1 {
201            return Err(Error::schema(
202                ErrorCode::AttributeCardinality,
203                FIELD,
204                attribute.span().start,
205            ));
206        }
207        let mut reader = attribute
208            .value_reader_scoped(0, &execution, FIELD)?
209            .ok_or_else(|| {
210                Error::schema(
211                    ErrorCode::AttributeCardinality,
212                    FIELD,
213                    attribute.span().start,
214                )
215            })?;
216        Self::read(&mut reader)
217    }
218
219    /// Original whole syntax value, without canonicalization.
220    #[must_use]
221    pub fn element(&self) -> Element<'a> {
222        self.element
223    }
224    /// Original policyAuthority names, separate from AC issuer/trust.
225    #[must_use]
226    pub fn authority(&self) -> Option<&[GeneralName<'a>]> {
227        self.authority.as_deref()
228    }
229    /// Original IMPLICIT authority encoding, including its actual source range.
230    #[must_use]
231    pub fn authority_element(&self) -> Option<Element<'a>> {
232        self.authority_element
233    }
234    /// Original values in order. Callers admit their own traversal; reading
235    /// this borrowed slice neither copies storage nor performs matching.
236    #[must_use]
237    pub fn values(&self) -> &[Member<'a>] {
238        &self.values
239    }
240    /// Common syntax, or None for an empty original values sequence.
241    #[must_use]
242    pub fn choice(&self) -> Option<Choice> {
243        self.choice
244    }
245}