Skip to main content

structured_cms/attribute_certificate/
service.rs

1//! Original service authentication and access identities (RFC5755 §§4.4.1–4.4.2).
2//!
3//! These are untrusted assertions until the application validates the AC and
4//! authenticates or authorizes its holder. No service is contacted and no
5//! service-specific credentials are interpreted, copied or logged here.
6//! <https://www.rfc-editor.org/rfc/rfc5755#section-4.4.1>
7
8use super::{end, sequence, tag};
9use crate::attribute::Attribute;
10use crate::signer::required;
11use crate::{Error, ErrorCode, Result};
12use core::fmt;
13use structured_asn1::memory::{OwnedVec, Work};
14use structured_asn1::{Element, Encoding, Oid, ScopedReader, Tag, universal};
15use structured_pkix::general_name::{self, GeneralName};
16
17const FIELD: &str = "attributeCertificate.svceAuthInfo";
18const SERVICE: &str = "attributeCertificate.svceAuthInfo.service";
19const IDENT: &str = "attributeCertificate.svceAuthInfo.ident";
20const AUTH: &str = "attributeCertificate.svceAuthInfo.authInfo";
21
22/// `id-aca-authenticationInfo` (1.3.6.1.5.5.7.10.1), RFC5755 §4.4.1.
23pub const AUTHENTICATION_INFO: Oid<'static> = Oid::from_bytes(&[43, 6, 1, 5, 5, 7, 10, 1]);
24/// `id-aca-accessIdentity` (1.3.6.1.5.5.7.10.2), RFC5755 §4.4.2.
25pub const ACCESS_IDENTITY: Oid<'static> = Oid::from_bytes(&[43, 6, 1, 5, 5, 7, 10, 2]);
26
27// Wire registration, not deployment policy or an authentication decision.
28#[derive(Clone, Copy)]
29enum Syntax {
30    Authentication,
31    Access,
32}
33
34/// A complete DER `SvceAuthInfo`, borrowing original input. Name metadata
35/// retains its capacity charge independently of the shorter query. `Debug`
36/// prints only source ranges, never names or potential credential bytes.
37pub struct SvceAuthInfo<'a> {
38    element: Element<'a>,
39    service_element: Element<'a>,
40    service: GeneralName<'a>,
41    ident_element: Element<'a>,
42    ident: GeneralName<'a>,
43    auth_info: Option<Element<'a>>,
44}
45
46impl fmt::Debug for SvceAuthInfo<'_> {
47    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
48        // §4.4.1's opaque authInfo can carry a password. Formatting metadata
49        // must not implicitly traverse or disclose any original payload.
50        formatter
51            .debug_struct("SvceAuthInfo")
52            .field("element", &self.element.span())
53            .field("service", &self.service_element.span())
54            .field("ident", &self.ident_element.span())
55            .field("auth_info", &self.auth_info.map(Element::span))
56            .finish()
57    }
58}
59
60impl<'a> SvceAuthInfo<'a> {
61    /// Reads exactly one standalone value. Optional authInfo is permitted by
62    /// this syntax; [`Self::read_attribute`] enforces its registered placement.
63    pub fn read(parent: &mut ScopedReader<'a, '_>) -> Result<Self> {
64        let element = required(parent, FIELD)?;
65        let reading = Self::read_element(parent, &element)?;
66        end(parent, FIELD)?;
67        Ok(reading)
68    }
69
70    /// Reads an original whole value as DER (RFC5755 §4.1), preserving its
71    /// source offsets, depth and current execution, without a replacement Parser.
72    pub fn read_element(parent: &ScopedReader<'a, '_>, element: &Element<'a>) -> Result<Self> {
73        Self::read_fields(parent, element, Syntax::Authentication)
74    }
75
76    fn read_fields(
77        parent: &ScopedReader<'a, '_>,
78        element: &Element<'a>,
79        syntax: Syntax,
80    ) -> Result<Self> {
81        let mut der = parent
82            .reread(element, Encoding::Der)
83            .map_err(|e| Error::encoding(e, FIELD))?;
84        let element = required(&mut der, FIELD)?;
85        tag(&element, sequence(), FIELD)?;
86        end(&der, FIELD)?;
87        let mut inside = der.enter(&element).map_err(|e| Error::encoding(e, FIELD))?;
88        let service_element = required(&mut inside, SERVICE)?;
89        let ident_element = required(&mut inside, IDENT)?;
90        let auth_info = inside
91            .next_element()
92            .map_err(|e| Error::encoding(e, AUTH))?;
93        if let Some(auth) = auth_info {
94            tag(&auth, Tag::primitive(universal::OCTET_STRING), AUTH)?;
95            // RFC5755 §4.4.2 forbids presence, including an empty OCTET STRING.
96            // Apply the registration before Name allocations or any payload use.
97            // https://www.rfc-editor.org/rfc/rfc5755#section-4.4.2
98            if matches!(syntax, Syntax::Access) {
99                return Err(Error::schema(
100                    ErrorCode::AttributeSyntax,
101                    AUTH,
102                    auth.span().start,
103                ));
104            }
105        }
106        end(&inside, FIELD)?;
107        let service =
108            general_name::read(&inside, &service_element, SERVICE).map_err(Error::pkix)?;
109        let ident = general_name::read(&inside, &ident_element, IDENT).map_err(Error::pkix)?;
110        Ok(Self {
111            element,
112            service_element,
113            service,
114            ident_element,
115            ident,
116            auth_info,
117        })
118    }
119
120    /// Reads every value of a registered authenticationInfo or accessIdentity
121    /// Attribute. Both permit multiple values; empty catalogues refuse. All
122    /// original values are checked before success, with no partial catalogue.
123    /// accessIdentity forbids authInfo; standalone syntax does not infer that
124    /// registration. This establishes neither authentication nor authorization.
125    pub fn read_attribute<W: Work>(attribute: &Attribute<'a>, work: &W) -> Result<OwnedVec<Self>> {
126        let execution = attribute.query_in(work);
127        // Two fixed-width OID comparisons and cardinality metadata. Longer
128        // unknown identifiers refuse on length, without an arbitrary scan.
129        execution
130            .charge_work(17)
131            .map_err(|e| Error::resource(e, FIELD, attribute.span().start))?;
132        let syntax = if attribute.kind() == AUTHENTICATION_INFO {
133            Syntax::Authentication
134        } else if attribute.kind() == ACCESS_IDENTITY {
135            Syntax::Access
136        } else {
137            return Err(Error::schema(
138                ErrorCode::UnexpectedContentType,
139                FIELD,
140                attribute.span().start,
141            ));
142        };
143        // RFC5755 §4.1's Attribute definition requires at least one value,
144        // independently of §4.2.7's nonempty outer attributes SEQUENCE.
145        // https://www.rfc-editor.org/rfc/rfc5755#section-4.1
146        if attribute.values().is_empty() {
147            return Err(Error::schema(
148                ErrorCode::AttributeCardinality,
149                FIELD,
150                attribute.span().start,
151            ));
152        }
153        let mut values = execution.vector();
154        values
155            .try_reserve_exact(attribute.values().len())
156            .map_err(|e| Error::resource(e, FIELD, attribute.span().start))?;
157        for index in 0..attribute.values().len() {
158            let mut reader = attribute
159                .value_reader_scoped(index, &execution, FIELD)?
160                .ok_or_else(|| {
161                    Error::schema(
162                        ErrorCode::AttributeCardinality,
163                        FIELD,
164                        attribute.span().start,
165                    )
166                })?;
167            let element = required(&mut reader, FIELD)?;
168            let value = Self::read_fields(&reader, &element, syntax)?;
169            end(&reader, FIELD)?;
170            values
171                .try_push(value)
172                .map_err(|e| Error::resource(e, FIELD, element.span().start))?;
173        }
174        Ok(values.into_owned())
175    }
176
177    /// Original whole value. It may contain sensitive authentication data.
178    #[must_use]
179    pub fn element(&self) -> Element<'a> {
180        self.element
181    }
182    /// Original decoded service name; no resolution or authentication is done.
183    #[must_use]
184    pub fn service(&self) -> &GeneralName<'a> {
185        &self.service
186    }
187    /// Original service CHOICE encoding and source range.
188    #[must_use]
189    pub fn service_element(&self) -> Element<'a> {
190        self.service_element
191    }
192    /// Original decoded holder identifier, not a proven identity.
193    #[must_use]
194    pub fn ident(&self) -> &GeneralName<'a> {
195        &self.ident
196    }
197    /// Original holder identifier CHOICE encoding and source range.
198    #[must_use]
199    pub fn ident_element(&self) -> Element<'a> {
200        self.ident_element
201    }
202    /// Original opaque authentication bytes, without interpreting or copying
203    /// them. None differs from a present empty OCTET STRING.
204    #[must_use]
205    pub fn auth_info(&self) -> Option<&'a [u8]> {
206        self.auth_info.map(Element::contents)
207    }
208    /// Original optional OCTET STRING, including its physical source range.
209    #[must_use]
210    pub fn auth_info_element(&self) -> Option<Element<'a>> {
211        self.auth_info
212    }
213}