Skip to main content
This is unreleased documentation for the main (development) branch of crypto-glue.

x509_cert/
attr.rs

1//! Attribute-related definitions as defined in X.501 (and updated by RFC 5280).
2
3use alloc::vec::Vec;
4use const_oid::db::{
5    DB, Database,
6    rfc3280::EMAIL_ADDRESS,
7    rfc4519::{COUNTRY_NAME, DOMAIN_COMPONENT, SERIAL_NUMBER},
8};
9use core::{
10    fmt::{self, Write},
11    str::FromStr,
12};
13use der::{
14    Decode, Encode, Error, ErrorKind, Sequence, Tag, Tagged, ValueOrd,
15    asn1::{
16        Any, Ia5StringRef, ObjectIdentifier, PrintableStringRef, SetOfVec, TeletexStringRef,
17        Utf8StringRef,
18    },
19};
20
21/// X.501 `AttributeType` as defined in [RFC 5280 Appendix A.1].
22///
23/// ```text
24/// AttributeType           ::= OBJECT IDENTIFIER
25/// ```
26///
27/// [RFC 5280 Appendix A.1]: https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1
28pub type AttributeType = ObjectIdentifier;
29
30/// X.501 `AttributeValue` as defined in [RFC 5280 Appendix A.1].
31///
32/// ```text
33/// AttributeValue          ::= ANY
34/// ```
35///
36/// [RFC 5280 Appendix A.1]: https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1
37pub type AttributeValue = Any;
38
39/// X.501 `Attribute` as defined in [RFC 5280 Appendix A.1].
40///
41/// ```text
42/// Attribute               ::= SEQUENCE {
43///     type             AttributeType,
44///     values    SET OF AttributeValue -- at least one value is required
45/// }
46/// ```
47///
48/// Note that [RFC 2986 Section 4] defines a constrained version of this type:
49///
50/// ```text
51/// Attribute { ATTRIBUTE:IOSet } ::= SEQUENCE {
52///     type   ATTRIBUTE.&id({IOSet}),
53///     values SET SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{@type})
54/// }
55/// ```
56///
57/// The unconstrained version should be preferred.
58///
59/// [RFC 2986 Section 4]: https://datatracker.ietf.org/doc/html/rfc2986#section-4
60/// [RFC 5280 Appendix A.1]: https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1
61#[derive(Clone, Debug, PartialEq, Eq, Sequence, ValueOrd)]
62#[allow(missing_docs)]
63pub struct Attribute {
64    pub oid: AttributeType,
65    pub values: SetOfVec<AttributeValue>,
66}
67
68/// X.501 `Attributes` as defined in [RFC 2986 Section 4].
69///
70/// ```text
71/// Attributes { ATTRIBUTE:IOSet } ::= SET OF Attribute{{ IOSet }}
72/// ```
73///
74/// [RFC 2986 Section 4]: https://datatracker.ietf.org/doc/html/rfc2986#section-4
75pub type Attributes = SetOfVec<Attribute>;
76
77/// X.501 `AttributeTypeAndValue` as defined in [RFC 5280 Appendix A.1].
78///
79/// ```text
80/// AttributeTypeAndValue ::= SEQUENCE {
81///   type     AttributeType,
82///   value    AttributeValue
83/// }
84/// ```
85///
86/// [RFC 5280 Appendix A.1]: https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1
87#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
88#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Sequence, ValueOrd, Hash)]
89#[allow(missing_docs)]
90pub struct AttributeTypeAndValue {
91    pub oid: AttributeType,
92    pub value: AttributeValue,
93}
94
95#[derive(Copy, Clone)]
96enum Escape {
97    None,
98    Some,
99    Hex(u8),
100}
101
102struct Parser {
103    state: Escape,
104    bytes: Vec<u8>,
105}
106
107impl Parser {
108    pub fn new() -> Self {
109        Self {
110            state: Escape::None,
111            bytes: Vec::new(),
112        }
113    }
114
115    fn push(&mut self, c: u8) {
116        self.state = Escape::None;
117        self.bytes.push(c);
118    }
119
120    pub fn add(&mut self, c: u8) -> Result<(), Error> {
121        match (self.state, c) {
122            (Escape::Hex(p), b'0'..=b'9') => self.push(p | (c - b'0')),
123            (Escape::Hex(p), b'a'..=b'f') => self.push(p | (c - b'a' + 10)),
124            (Escape::Hex(p), b'A'..=b'F') => self.push(p | (c - b'A' + 10)),
125
126            (Escape::Some, b'0'..=b'9') => self.state = Escape::Hex((c - b'0') << 4),
127            (Escape::Some, b'a'..=b'f') => self.state = Escape::Hex((c - b'a' + 10) << 4),
128            (Escape::Some, b'A'..=b'F') => self.state = Escape::Hex((c - b'A' + 10) << 4),
129
130            (Escape::Some, b' ' | b'"' | b'#' | b'=' | b'\\') => self.push(c),
131            (Escape::Some, b'+' | b',' | b';' | b'<' | b'>') => self.push(c),
132
133            (Escape::None, b'\\') => self.state = Escape::Some,
134            (Escape::None, ..) => self.push(c),
135
136            _ => return Err(ErrorKind::Failed.into()),
137        }
138
139        Ok(())
140    }
141
142    pub fn as_bytes(&self) -> &[u8] {
143        &self.bytes
144    }
145}
146
147impl AttributeTypeAndValue {
148    /// Parses the hex value in the `OID=#HEX` format.
149    fn from_hex(oid: ObjectIdentifier, val: &str) -> Result<Self, Error> {
150        // Ensure an even number of hex bytes.
151        let mut iter = match val.len() % 2 {
152            0 => [].iter().cloned().chain(val.bytes()),
153            1 => [0u8].iter().cloned().chain(val.bytes()),
154            _ => unreachable!(),
155        };
156
157        // Decode der bytes from hex.
158        let mut bytes = Vec::with_capacity(val.len().div_ceil(2));
159
160        while let (Some(h), Some(l)) = (iter.next(), iter.next()) {
161            let mut byte = 0u8;
162
163            for (half, shift) in [(h, 4), (l, 0)] {
164                match half {
165                    b'0'..=b'9' => byte |= (half - b'0') << shift,
166                    b'a'..=b'f' => byte |= (half - b'a' + 10) << shift,
167                    b'A'..=b'F' => byte |= (half - b'A' + 10) << shift,
168                    _ => return Err(ErrorKind::Failed.into()),
169                }
170            }
171
172            bytes.push(byte);
173        }
174
175        Ok(Self {
176            oid,
177            value: Any::from_der(&bytes)?,
178        })
179    }
180
181    /// Parses the string value in the `NAME=STRING` format.
182    fn from_delimited_str(oid: ObjectIdentifier, val: &str) -> Result<Self, Error> {
183        // Undo escaping.
184        let mut parser = Parser::new();
185        for c in val.bytes() {
186            parser.add(c)?;
187        }
188
189        let tag = match oid {
190            COUNTRY_NAME => Tag::PrintableString,
191            DOMAIN_COMPONENT => Tag::Ia5String,
192            // Serial numbers are formatted as Printable String as per RFC 5280 Appendix A.1:
193            // https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1
194            SERIAL_NUMBER => Tag::PrintableString,
195            // Email addresses are formatted as Ia5String as per RFC 5280 Appendix A.1:
196            // https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1
197            EMAIL_ADDRESS => Tag::Ia5String,
198            _ => Tag::Utf8String,
199        };
200
201        Ok(Self {
202            oid,
203            value: Any::new(tag, parser.as_bytes())?,
204        })
205    }
206
207    /// Converts an AttributeTypeAndValue string into an encoded AttributeTypeAndValue
208    ///
209    /// This function follows the rules in [RFC 4514].
210    ///
211    /// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514
212    #[deprecated(
213        since = "0.2.1",
214        note = "use AttributeTypeAndValue::from_str(...)?.to_der()"
215    )]
216    pub fn encode_from_string(s: &str) -> Result<Vec<u8>, Error> {
217        Self::from_str(s)?.to_der()
218    }
219}
220
221/// Parse an [`AttributeTypeAndValue`] string.
222///
223/// This function follows the rules in [RFC 4514].
224///
225/// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514
226impl FromStr for AttributeTypeAndValue {
227    type Err = Error;
228
229    fn from_str(s: &str) -> der::Result<Self> {
230        let idx = s.find('=').ok_or_else(|| Error::from(ErrorKind::Failed))?;
231        let (key, val) = s.split_at(idx);
232        let val = &val[1..];
233
234        // Either decode or lookup the OID for the given key.
235        let oid = match DB.by_name(key) {
236            Some(oid) => *oid,
237            None => ObjectIdentifier::new(key)?,
238        };
239
240        // If the value is hex-encoded DER...
241        match val.strip_prefix('#') {
242            Some(val) => Self::from_hex(oid, val),
243            None => Self::from_delimited_str(oid, val),
244        }
245    }
246}
247
248/// Serializes the structure according to the rules in [RFC 4514].
249///
250/// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514
251impl fmt::Display for AttributeTypeAndValue {
252    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253        let val = match self.value.tag() {
254            Tag::PrintableString => PrintableStringRef::try_from(&self.value)
255                .ok()
256                .map(|s| s.as_str()),
257            Tag::Utf8String => Utf8StringRef::try_from(&self.value)
258                .ok()
259                .map(|s| s.as_str()),
260            Tag::Ia5String => Ia5StringRef::try_from(&self.value).ok().map(|s| s.as_str()),
261            Tag::TeletexString => TeletexStringRef::try_from(&self.value)
262                .ok()
263                .map(|s| s.as_str()),
264            _ => None,
265        };
266
267        if let (Some(key), Some(val)) = (DB.shortest_name_by_oid(&self.oid), val) {
268            write!(f, "{}=", key.to_ascii_uppercase())?;
269
270            let mut iter = val.char_indices().peekable();
271            while let Some((i, c)) = iter.next() {
272                match c {
273                    '#' if i == 0 => write!(f, "\\#")?,
274                    ' ' if i == 0 || iter.peek().is_none() => write!(f, "\\ ")?,
275                    '"' | '+' | ',' | ';' | '<' | '>' | '\\' => write!(f, "\\{c}")?,
276                    '\x00'..='\x1f' | '\x7f' => write!(f, "\\{:02x}", c as u8)?,
277                    _ => f.write_char(c)?,
278                }
279            }
280        } else {
281            let value = self.value.to_der().or(Err(fmt::Error))?;
282
283            write!(f, "{}=#", self.oid)?;
284            for c in value {
285                write!(f, "{c:02x}")?;
286            }
287        }
288
289        Ok(())
290    }
291}
292
293/// Helper trait to bring shortest name by oid lookups to Database
294trait ShortestName {
295    fn shortest_name_by_oid(&self, oid: &ObjectIdentifier) -> Option<&str>;
296}
297
298impl<'a> ShortestName for Database<'a> {
299    fn shortest_name_by_oid(&self, oid: &ObjectIdentifier) -> Option<&'a str> {
300        let mut best_match: Option<&'a str> = None;
301
302        for m in self.find_names_for_oid(*oid) {
303            if let Some(previous) = best_match {
304                if m.len() < previous.len() {
305                    best_match = Some(m);
306                }
307            } else {
308                best_match = Some(m);
309            }
310        }
311
312        best_match
313    }
314}