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

x509_cert/
name.rs

1//! Name-related definitions as defined in X.501 (and updated by RFC 5280).
2
3use crate::{attr::AttributeTypeAndValue, ext::pkix::name::DirectoryString};
4use alloc::vec::Vec;
5use const_oid::{
6    ObjectIdentifier,
7    db::{rfc3280, rfc4519},
8};
9use core::{cmp::Ordering, fmt, str::FromStr};
10use der::{
11    DecodeValue, Encode, EncodeValue, FixedTag, Header, Length, Reader, Tag, ValueOrd, Writer,
12    asn1::{Any, Ia5StringRef, PrintableStringRef, SetOfVec},
13};
14
15/// X.501 Name as defined in [RFC 5280 Section 4.1.2.4]. X.501 Name is used to represent distinguished names.
16///
17/// ```text
18/// Name ::= CHOICE { rdnSequence  RDNSequence }
19/// ```
20///
21/// To build name, the syntax described in [RFC 4514 Section 3] is expected.
22///
23/// The following attribute names are recognized:
24/// ```text
25///      String  X.500 AttributeType
26///      ------  --------------------------------------------
27///      CN      commonName (2.5.4.3)
28///      L       localityName (2.5.4.7)
29///      ST      stateOrProvinceName (2.5.4.8)
30///      O       organizationName (2.5.4.10)
31///      OU      organizationalUnitName (2.5.4.11)
32///      C       countryName (2.5.4.6)
33///      STREET  streetAddress (2.5.4.9)
34///      DC      domainComponent (0.9.2342.19200300.100.1.25)
35///      UID     userId (0.9.2342.19200300.100.1.1)
36///
37/// ```
38///
39/// # Example
40///
41/// ```
42/// use std::str::FromStr;
43/// use x509_cert::name::Name;
44///
45/// // Multiple syntaxes are supported by `from_str`:
46/// let subject = Name::from_str("CN=example.com").unwrap();
47/// let subject = Name::from_str("C=US; ST=California; L=Los Angeles; O=InternetCorporationforAssignedNamesandNumbers; CN=www.example.org").unwrap();
48/// let subject = Name::from_str("C=US,ST=California,L=Los Angeles,O=InternetCorporationforAssignedNamesandNumbers,CN=www.example.org").unwrap();
49/// let subject = Name::from_str("C=US/ST=California/L=Los Angeles/O=InternetCorporationforAssignedNamesandNumbers/CN=www.example.org").unwrap();
50/// let subject = Name::from_str("UID=jsmith,DC=example,DC=net").unwrap();
51/// let subject = Name::from_str("OU=Sales+CN=J.  Smith,DC=example,DC=net").unwrap();
52/// let subject = Name::from_str(r#"CN=James \"Jim\" Smith\, III,DC=example,DC=net"#).unwrap();
53/// let subject = Name::from_str(r#"CN=Before\0dAfter,DC=example,DC=net"#).unwrap();
54/// let subject = Name::from_str("1.3.6.1.4.1.1466.0=#04024869").unwrap();
55/// ```
56///
57/// [RFC 4514 Section 3]: https://www.rfc-editor.org/rfc/rfc4514#section-3
58/// [RFC 5280 Section 4.1.2.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.4
59#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
60#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
61pub struct Name(pub(crate) RdnSequence);
62
63impl Name {
64    /// Build a name from an [`RdnSequence`].
65    ///
66    ///
67    /// This is provided as an escape hatch (see [RFC 5280 Section 4.1.2.4]) to build
68    /// names from `bmpString`, `TeletexString`, or `UniversalString`:
69    /// ```text
70    /// When CAs have previously issued certificates with issuer fields with
71    /// attributes encoded using TeletexString, BMPString, or
72    /// UniversalString, then the CA MAY continue to use these encodings of
73    /// the DirectoryString to preserve backward compatibility.
74    /// ```
75    ///
76    /// # Safety
77    ///
78    /// As the name implies, this is a dangerous helper. You are responsible for ensuring the
79    /// [`RdnSequence`] complies with the RFC.
80    ///
81    /// [RFC 5280 Section 4.1.2.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.4
82    #[cfg(feature = "hazmat")]
83    pub fn hazmat_from_rdn_sequence(value: RdnSequence) -> Self {
84        Self(value)
85    }
86}
87
88impl From<Name> for RdnSequence {
89    #[inline]
90    fn from(value: Name) -> Self {
91        value.0
92    }
93}
94
95impl AsRef<RdnSequence> for Name {
96    #[inline]
97    fn as_ref(&self) -> &RdnSequence {
98        &self.0
99    }
100}
101
102impl FixedTag for Name {
103    const TAG: Tag = <RdnSequence as FixedTag>::TAG;
104}
105
106impl<'a> DecodeValue<'a> for Name {
107    type Error = der::Error;
108
109    fn decode_value<R: Reader<'a>>(decoder: &mut R, header: Header) -> der::Result<Self> {
110        Ok(Self(RdnSequence::decode_value(decoder, header)?))
111    }
112}
113
114impl EncodeValue for Name {
115    fn encode_value(&self, encoder: &mut impl Writer) -> der::Result<()> {
116        self.0.encode_value(encoder)
117    }
118
119    fn value_len(&self) -> der::Result<Length> {
120        self.0.value_len()
121    }
122}
123
124impl ValueOrd for Name {
125    fn value_cmp(&self, other: &Self) -> der::Result<Ordering> {
126        self.0.value_cmp(&other.0)
127    }
128}
129
130impl Name {
131    /// Is this [`Name`] empty?
132    #[inline]
133    pub fn is_empty(&self) -> bool {
134        self.0.is_empty()
135    }
136
137    /// Returns the number of [`RelativeDistinguishedName`] elements in this [`Name`].
138    pub fn len(&self) -> usize {
139        self.0.0.len()
140    }
141
142    /// Returns an iterator over the inner [`AttributeTypeAndValue`]s.
143    ///
144    /// This iterator does not expose which attributes are grouped together as
145    /// [`RelativeDistinguishedName`]s. If you need this, use [`Self::iter_rdn`].
146    #[inline]
147    pub fn iter(&self) -> impl Iterator<Item = &'_ AttributeTypeAndValue> + '_ {
148        self.0.0.iter().flat_map(move |rdn| rdn.0.as_slice())
149    }
150
151    /// Returns an iterator over the inner [`RelativeDistinguishedName`]s.
152    #[inline]
153    pub fn iter_rdn(&self) -> impl Iterator<Item = &'_ RelativeDistinguishedName> + '_ {
154        self.0.0.iter()
155    }
156}
157
158impl Name {
159    /// Returns the element found in the name identified by `oid`
160    ///
161    /// This will return `Ok(None)` if no such element is present.
162    ///
163    /// If more than one attribute is present with the specified OID, only the first attribute is
164    /// returned. Later elements should be fetched using [`Name::iter`].
165    ///
166    /// # Errors
167    ///
168    /// This will return [`der::Error`] if the content is not serialized as expected
169    pub fn by_oid<'a, T>(&'a self, oid: ObjectIdentifier) -> der::Result<Option<T>>
170    where
171        T: TryFrom<&'a Any, Error = der::Error>,
172        T: fmt::Debug,
173    {
174        self.iter()
175            .filter(|atav| atav.oid == oid)
176            .map(|atav| T::try_from(&atav.value))
177            .next()
178            .transpose()
179    }
180
181    /// Returns the Common Name (CN) found in the name.
182    ///
183    /// This will return `Ok(None)` if no CN is found.
184    ///
185    /// If more than one value is present, only the first is returned.
186    /// Later elements should be fetched using [`Name::iter`].
187    ///
188    /// # Errors
189    ///
190    /// This will return [`der::Error`] if the content is not serialized as a string.
191    pub fn common_name(&self) -> der::Result<Option<DirectoryString>> {
192        self.by_oid(rfc4519::COMMON_NAME)
193    }
194
195    /// Returns the Country (C) found in the name.
196    ///
197    /// This will return `Ok(None)` if no Country is found.
198    ///
199    /// If more than one value is present, only the first is returned.
200    /// Later elements should be fetched using [`Name::iter`].
201    ///
202    /// # Errors
203    ///
204    /// This will return [`der::Error`] if the content is not serialized as a printableString.
205    pub fn country(&self) -> der::Result<Option<PrintableStringRef<'_>>> {
206        self.by_oid(rfc4519::COUNTRY_NAME)
207    }
208
209    /// Returns the State or Province (ST) found in the name.
210    ///
211    /// This will return `Ok(None)` if no State or Province is found.
212    ///
213    /// If more than one value is present, only the first is returned.
214    /// Later elements should be fetched using [`Name::iter`].
215    ///
216    /// # Errors
217    ///
218    /// This will return [`der::Error`] if the content is not serialized as a string.
219    pub fn state_or_province(&self) -> der::Result<Option<DirectoryString>> {
220        self.by_oid(rfc4519::ST)
221    }
222
223    /// Returns the Locality (L) found in the name.
224    ///
225    /// This will return `Ok(None)` if no Locality is found.
226    ///
227    /// If more than one value is present, only the first is returned.
228    /// Later elements should be fetched using [`Name::iter`].
229    ///
230    /// # Errors
231    ///
232    /// This will return [`der::Error`] if the content is not serialized as a string.
233    pub fn locality(&self) -> der::Result<Option<DirectoryString>> {
234        self.by_oid(rfc4519::LOCALITY_NAME)
235    }
236
237    /// Returns the Organization (O) found in the name.
238    ///
239    /// This will return `Ok(None)` if no Organization is found.
240    ///
241    /// If more than one value is present, only the first is returned.
242    /// Later elements should be fetched using [`Name::iter`].
243    ///
244    /// # Errors
245    ///
246    /// This will return [`der::Error`] if the content is not serialized as a string.
247    pub fn organization(&self) -> der::Result<Option<DirectoryString>> {
248        self.by_oid(rfc4519::ORGANIZATION_NAME)
249    }
250
251    /// Returns the Organization Unit (OU) found in the name.
252    ///
253    /// This will return `Ok(None)` if no Organization Unit is found.
254    ///
255    /// If more than one value is present, only the first is returned.
256    /// Later elements should be fetched using [`Name::iter`].
257    ///
258    /// # Errors
259    ///
260    /// This will return [`der::Error`] if the content is not serialized as a string.
261    pub fn organization_unit(&self) -> der::Result<Option<DirectoryString>> {
262        self.by_oid(rfc4519::ORGANIZATIONAL_UNIT_NAME)
263    }
264
265    /// Returns the Email Address (emailAddress) found in the name.
266    ///
267    /// This will return `Ok(None)` if no email address is found.
268    ///
269    /// If more than one value is present, only the first is returned.
270    /// Later elements should be fetched using [`Name::iter`].
271    ///
272    /// # Errors
273    ///
274    /// This will return [`der::Error`] if the content is not serialized as an ia5String.
275    pub fn email_address(&self) -> der::Result<Option<Ia5StringRef<'_>>> {
276        self.by_oid(rfc3280::EMAIL_ADDRESS)
277    }
278}
279
280/// Parse a [`Name`] string.
281///
282/// Follows the rules in [RFC 4514].
283///
284/// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514
285impl FromStr for Name {
286    type Err = der::Error;
287
288    fn from_str(s: &str) -> der::Result<Self> {
289        Ok(Self(RdnSequence::from_str(s)?))
290    }
291}
292
293/// Serializes the name according to the rules in [RFC 4514].
294///
295/// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514
296impl fmt::Display for Name {
297    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
298        self.0.fmt(f)
299    }
300}
301
302/// X.501 RDNSequence as defined in [RFC 5280 Section 4.1.2.4].
303///
304/// ```text
305/// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
306/// ```
307///
308/// [RFC 5280 Section 4.1.2.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.4
309#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
310#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
311pub struct RdnSequence(Vec<RelativeDistinguishedName>);
312
313impl RdnSequence {
314    /// Converts an `RDNSequence` string into an encoded `RDNSequence`.
315    #[deprecated(since = "0.2.1", note = "use RdnSequence::from_str(...)?.to_der()")]
316    pub fn encode_from_string(s: &str) -> Result<Vec<u8>, der::Error> {
317        Self::from_str(s)?.to_der()
318    }
319
320    /// Is this [`RdnSequence`] empty?
321    pub fn is_empty(&self) -> bool {
322        self.0.is_empty()
323    }
324
325    /// Iterate over this [`RdnSequence`].
326    pub fn iter(&self) -> impl Iterator<Item = &RelativeDistinguishedName> {
327        self.0.iter()
328    }
329
330    /// Length of this [`RdnSequence`].
331    pub fn len(&self) -> usize {
332        self.0.len()
333    }
334
335    /// Push a [`RelativeDistinguishedName`] onto this [`RdnSequence`].
336    pub fn push(&mut self, name: RelativeDistinguishedName) {
337        self.0.push(name)
338    }
339}
340
341/// Parse an [`RdnSequence`] string.
342///
343/// Follows the rules in [RFC 4514].
344///
345/// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514
346impl FromStr for RdnSequence {
347    type Err = der::Error;
348
349    fn from_str(s: &str) -> der::Result<Self> {
350        let mut parts = split(s, b',')
351            .map(RelativeDistinguishedName::from_str)
352            .collect::<der::Result<Vec<_>>>()?;
353        parts.reverse();
354        Ok(Self(parts))
355    }
356}
357
358/// Serializes the structure according to the rules in [RFC 4514].
359///
360/// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514
361impl fmt::Display for RdnSequence {
362    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
363        // As per RFC 4514 Section 2.1, the elements are reversed
364        for (i, atv) in self.0.iter().rev().enumerate() {
365            match i {
366                0 => write!(f, "{atv}")?,
367                _ => write!(f, ",{atv}")?,
368            }
369        }
370
371        Ok(())
372    }
373}
374
375impl_newtype!(RdnSequence, Vec<RelativeDistinguishedName>);
376
377/// Find the indices of all non-escaped separators.
378fn find(s: &str, b: u8) -> impl '_ + Iterator<Item = usize> {
379    (0..s.len())
380        .filter(move |i| s.as_bytes()[*i] == b)
381        .filter(|i| {
382            let x = i
383                .checked_sub(2)
384                .map(|i| s.as_bytes()[i])
385                .unwrap_or_default();
386
387            let y = i
388                .checked_sub(1)
389                .map(|i| s.as_bytes()[i])
390                .unwrap_or_default();
391
392            y != b'\\' || x == b'\\'
393        })
394}
395
396/// Split a string at all non-escaped separators.
397fn split(s: &str, b: u8) -> impl '_ + Iterator<Item = &'_ str> {
398    let mut prev = 0;
399    find(s, b).chain([s.len()]).map(move |i| {
400        let x = &s[prev..i];
401        prev = i + 1;
402        x
403    })
404}
405
406/// X.501 DistinguishedName as defined in [RFC 5280 Section 4.1.2.4].
407///
408/// ```text
409/// DistinguishedName ::=   RDNSequence
410/// ```
411///
412/// [RFC 5280 Section 4.1.2.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.4
413pub type DistinguishedName = RdnSequence;
414
415/// RelativeDistinguishedName as defined in [RFC 5280 Section 4.1.2.4].
416///
417/// ```text
418/// RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue
419/// ```
420///
421/// Note that we follow the more common definition above. This technically
422/// differs from the definition in X.501, which is:
423///
424/// ```text
425/// RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndDistinguishedValue
426///
427/// AttributeTypeAndDistinguishedValue ::= SEQUENCE {
428///     type ATTRIBUTE.&id ({SupportedAttributes}),
429///     value ATTRIBUTE.&Type({SupportedAttributes}{@type}),
430///     primaryDistinguished BOOLEAN DEFAULT TRUE,
431///     valuesWithContext SET SIZE (1..MAX) OF SEQUENCE {
432///         distingAttrValue [0] ATTRIBUTE.&Type ({SupportedAttributes}{@type}) OPTIONAL,
433///         contextList SET SIZE (1..MAX) OF Context
434///     } OPTIONAL
435/// }
436/// ```
437///
438/// [RFC 5280 Section 4.1.2.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.4
439#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
440#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
441pub struct RelativeDistinguishedName(pub(crate) SetOfVec<AttributeTypeAndValue>);
442
443impl RelativeDistinguishedName {
444    /// Is this [`RelativeDistinguishedName`] empty?
445    pub fn is_empty(&self) -> bool {
446        self.0.is_empty()
447    }
448
449    /// Iterate over this [`RelativeDistinguishedName`].
450    pub fn iter(&self) -> impl Iterator<Item = &AttributeTypeAndValue> {
451        self.0.iter()
452    }
453
454    /// Length of this [`RelativeDistinguishedName`].
455    pub fn len(&self) -> usize {
456        self.0.len()
457    }
458
459    /// Insert an [`AttributeTypeAndValue`] into this [`RelativeDistinguishedName`]. Must be unique.
460    pub fn insert(&mut self, item: AttributeTypeAndValue) -> Result<(), der::Error> {
461        self.0.insert(item)
462    }
463}
464
465/// Parse a [`RelativeDistinguishedName`] string.
466///
467/// This function follows the rules in [RFC 4514].
468///
469/// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514
470impl FromStr for RelativeDistinguishedName {
471    type Err = der::Error;
472
473    fn from_str(s: &str) -> der::Result<Self> {
474        split(s, b'+')
475            .map(AttributeTypeAndValue::from_str)
476            .collect::<der::Result<Vec<_>>>()?
477            .try_into()
478            .map(Self)
479    }
480}
481
482impl TryFrom<Vec<AttributeTypeAndValue>> for RelativeDistinguishedName {
483    type Error = der::Error;
484
485    fn try_from(vec: Vec<AttributeTypeAndValue>) -> der::Result<RelativeDistinguishedName> {
486        Ok(RelativeDistinguishedName(SetOfVec::try_from(vec)?))
487    }
488}
489
490/// Serializes the structure according to the rules in [RFC 4514].
491///
492/// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514
493impl fmt::Display for RelativeDistinguishedName {
494    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
495        for (i, atv) in self.0.iter().enumerate() {
496            match i {
497                0 => write!(f, "{atv}")?,
498                _ => write!(f, "+{atv}")?,
499            }
500        }
501
502        Ok(())
503    }
504}
505
506impl_newtype!(RelativeDistinguishedName, SetOfVec<AttributeTypeAndValue>);