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