picky_asn1_x509/
name.rs

1use crate::{AttributeTypeAndValue, AttributeTypeAndValueParameters, DirectoryString};
2use picky_asn1::tag::{Encoding, Tag, TagClass, TagPeeker};
3use picky_asn1::wrapper::*;
4use serde::{de, ser, Deserialize, Serialize};
5use std::fmt;
6
7#[derive(Clone, Debug, PartialEq, Eq)]
8pub enum NameAttr {
9    CommonName,
10    Surname,
11    SerialNumber,
12    CountryName,
13    LocalityName,
14    StateOrProvinceName,
15    StreetName,
16    OrganizationName,
17    OrganizationalUnitName,
18    GivenName,
19    Phone,
20}
21
22/// [RFC 5280 #4.1.2.4](https://tools.ietf.org/html/rfc5280#section-4.1.2.4)
23///
24/// ```not_rust
25/// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
26/// ```
27pub type RdnSequence = Asn1SequenceOf<RelativeDistinguishedName>;
28
29/// [RFC 5280 #4.1.2.4](https://tools.ietf.org/html/rfc5280#section-4.1.2.4)
30///
31/// ```not_rust
32/// RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue
33/// ```
34pub type RelativeDistinguishedName = Asn1SetOf<AttributeTypeAndValue>;
35
36/// [RFC 5280 #4.2.1.6](https://tools.ietf.org/html/rfc5280#section-4.2.1.6)
37///
38/// ```not_rust
39/// DirectoryName ::= Name
40/// ```
41pub type DirectoryName = Name;
42
43/// [RFC 5280 #4.1.2.4](https://tools.ietf.org/html/rfc5280#section-4.1.2.4)
44///
45/// ```not_rust
46/// Name ::= CHOICE { -- only one possibility for now --
47///       rdnSequence  RDNSequence }
48/// ```
49#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
50pub struct Name(pub RdnSequence);
51
52impl Default for Name {
53    fn default() -> Self {
54        Self::new()
55    }
56}
57
58impl Name {
59    pub fn new() -> Self {
60        Self(Asn1SequenceOf(Vec::new()))
61    }
62
63    pub fn new_common_name<S: Into<DirectoryString>>(name: S) -> Self {
64        let mut dn = Self::default();
65        dn.add_attr(NameAttr::CommonName, name);
66        dn
67    }
68
69    /// Find the first common name contained in this `Name`
70    pub fn find_common_name(&self) -> Option<&DirectoryString> {
71        for relative_distinguished_name in &((self.0).0) {
72            for attr_ty_val in &relative_distinguished_name.0 {
73                if let AttributeTypeAndValueParameters::CommonName(dir_string) = &attr_ty_val.value {
74                    return Some(dir_string);
75                }
76            }
77        }
78        None
79    }
80
81    pub fn add_attr<S: Into<DirectoryString>>(&mut self, attr: NameAttr, value: S) {
82        let ty_val = match attr {
83            NameAttr::CommonName => AttributeTypeAndValue::new_common_name(value),
84            NameAttr::Surname => AttributeTypeAndValue::new_surname(value),
85            NameAttr::SerialNumber => AttributeTypeAndValue::new_serial_number(value),
86            NameAttr::CountryName => AttributeTypeAndValue::new_country_name(value),
87            NameAttr::LocalityName => AttributeTypeAndValue::new_locality_name(value),
88            NameAttr::StateOrProvinceName => AttributeTypeAndValue::new_state_or_province_name(value),
89            NameAttr::StreetName => AttributeTypeAndValue::new_street_name(value),
90            NameAttr::OrganizationName => AttributeTypeAndValue::new_organization_name(value),
91            NameAttr::OrganizationalUnitName => AttributeTypeAndValue::new_organizational_unit_name(value),
92            NameAttr::GivenName => AttributeTypeAndValue::new_given_name(value),
93            NameAttr::Phone => AttributeTypeAndValue::new_phone(value),
94        };
95        let set_val = Asn1SetOf(vec![ty_val]);
96        ((self.0).0).push(set_val);
97    }
98
99    /// Add an emailAddress attribute.
100    /// NOTE: this attribute does not conform with the RFC 5280, email should be placed in SAN instead
101    pub fn add_email<S: Into<Ia5StringAsn1>>(&mut self, value: S) {
102        let set_val = Asn1SetOf(vec![AttributeTypeAndValue::new_email_address(value)]);
103        ((self.0).0).push(set_val);
104    }
105}
106
107impl fmt::Display for Name {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        NamePrettyFormatter(self).fmt(f)
110    }
111}
112
113pub struct NamePrettyFormatter<'a>(pub &'a Name);
114
115impl fmt::Display for NamePrettyFormatter<'_> {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        let mut first = true;
118        for name in &((self.0).0).0 {
119            for attr in &name.0 {
120                if first {
121                    first = false;
122                } else {
123                    write!(f, ",")?;
124                }
125
126                match &attr.value {
127                    AttributeTypeAndValueParameters::CommonName(name) => {
128                        write!(f, "CN={}", name)?;
129                    }
130                    AttributeTypeAndValueParameters::Surname(name) => {
131                        write!(f, "SURNAME={}", name)?;
132                    }
133                    AttributeTypeAndValueParameters::SerialNumber(name) => {
134                        write!(f, "SN={}", name)?;
135                    }
136                    AttributeTypeAndValueParameters::CountryName(name) => {
137                        write!(f, "C={}", name)?;
138                    }
139                    AttributeTypeAndValueParameters::LocalityName(name) => {
140                        write!(f, "L={}", name)?;
141                    }
142                    AttributeTypeAndValueParameters::StateOrProvinceName(name) => {
143                        write!(f, "ST={}", name)?;
144                    }
145                    AttributeTypeAndValueParameters::StreetName(name) => {
146                        write!(f, "STREET NAME={}", name)?;
147                    }
148                    AttributeTypeAndValueParameters::OrganizationName(name) => {
149                        write!(f, "O={}", name)?;
150                    }
151                    AttributeTypeAndValueParameters::OrganizationalUnitName(name) => {
152                        write!(f, "OU={}", name)?;
153                    }
154                    AttributeTypeAndValueParameters::EmailAddress(name) => {
155                        write!(f, "EMAIL={}", String::from_utf8_lossy(name.as_bytes()))?;
156                    }
157                    AttributeTypeAndValueParameters::GivenName(name) => {
158                        write!(f, "GIVENNAME={}", name)?;
159                    }
160                    AttributeTypeAndValueParameters::Phone(name) => {
161                        write!(f, "PHONE={}", name)?;
162                    }
163                    AttributeTypeAndValueParameters::Custom(der) => {
164                        write!(f, "{}={:?}", Into::<String>::into(&attr.ty.0), der)?;
165                    }
166                }
167            }
168        }
169        Ok(())
170    }
171}
172
173/// [RFC 5280 #4.2.1.6](https://tools.ietf.org/html/rfc5280#section-4.2.1.6)
174///
175/// ```not_rust
176/// GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
177/// ```
178pub type GeneralNames = Asn1SequenceOf<GeneralName>;
179
180/// [RFC 5280 #4.2.1.6](https://tools.ietf.org/html/rfc5280#section-4.2.1.6)
181///
182/// ```not_rust
183/// GeneralName ::= CHOICE {
184///       otherName                       [0]     OtherName,
185///       rfc822Name                      [1]     IA5String,
186///       dNSName                         [2]     IA5String,
187///       x400Address                     [3]     ORAddress,
188///       directoryName                   [4]     Name,
189///       ediPartyName                    [5]     EDIPartyName,
190///       uniformResourceIdentifier       [6]     IA5String,
191///       iPAddress                       [7]     OCTET STRING,
192///       registeredID                    [8]     OBJECT IDENTIFIER }
193/// ```
194#[derive(Debug, PartialEq, Eq, Clone)]
195pub enum GeneralName {
196    OtherName(OtherName),
197    Rfc822Name(Ia5StringAsn1),
198    DnsName(Ia5StringAsn1),
199    //X400Address(ORAddress),
200    DirectoryName(Name),
201    EdiPartyName(EdiPartyName),
202    Uri(Ia5StringAsn1),
203    IpAddress(OctetStringAsn1),
204    RegisteredId(ObjectIdentifierAsn1),
205}
206
207impl GeneralName {
208    pub fn new_edi_party_name<PN, NA>(party_name: PN, name_assigner: Option<NA>) -> Self
209    where
210        PN: Into<DirectoryString>,
211        NA: Into<DirectoryString>,
212    {
213        Self::EdiPartyName(EdiPartyName {
214            name_assigner: Optional(name_assigner.map(Into::into).map(ImplicitContextTag0)),
215            party_name: ImplicitContextTag1(party_name.into()),
216        })
217    }
218}
219
220impl From<Name> for GeneralName {
221    fn from(name: Name) -> Self {
222        Self::DirectoryName(name)
223    }
224}
225
226impl ser::Serialize for GeneralName {
227    fn serialize<S>(&self, serializer: S) -> Result<<S as ser::Serializer>::Ok, <S as ser::Serializer>::Error>
228    where
229        S: ser::Serializer,
230    {
231        match &self {
232            GeneralName::OtherName(name) => {
233                let mut raw_der = picky_asn1_der::to_vec(name).map_err(ser::Error::custom)?;
234                raw_der[0] = Tag::context_specific_constructed(0).inner();
235                picky_asn1_der::Asn1RawDer(raw_der).serialize(serializer)
236            }
237            GeneralName::Rfc822Name(name) => ImplicitContextTag1(name).serialize(serializer),
238            GeneralName::DnsName(name) => ImplicitContextTag2(name).serialize(serializer),
239            GeneralName::DirectoryName(name) => ImplicitContextTag4(name).serialize(serializer),
240            GeneralName::EdiPartyName(name) => ImplicitContextTag5(name).serialize(serializer),
241            GeneralName::Uri(name) => ImplicitContextTag6(name).serialize(serializer),
242            GeneralName::IpAddress(name) => ImplicitContextTag7(name).serialize(serializer),
243            GeneralName::RegisteredId(name) => ImplicitContextTag8(name).serialize(serializer),
244        }
245    }
246}
247
248impl<'de> de::Deserialize<'de> for GeneralName {
249    fn deserialize<D>(deserializer: D) -> Result<Self, <D as de::Deserializer<'de>>::Error>
250    where
251        D: de::Deserializer<'de>,
252    {
253        struct Visitor;
254
255        impl<'de> de::Visitor<'de> for Visitor {
256            type Value = GeneralName;
257
258            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
259                formatter.write_str("a valid DER-encoded GeneralName")
260            }
261
262            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
263            where
264                A: de::SeqAccess<'de>,
265            {
266                let tag_peeker: TagPeeker = seq_next_element!(seq, DirectoryString, "choice tag");
267                match tag_peeker.next_tag.components() {
268                    (TagClass::ContextSpecific, Encoding::Primitive, 0) => Err(serde_invalid_value!(
269                        GeneralName,
270                        "Primitive encoding for OtherName not supported",
271                        "a supported choice"
272                    )),
273                    (TagClass::ContextSpecific, Encoding::Constructed, 0) => Ok(GeneralName::OtherName(
274                        seq_next_element!(seq, OtherName, GeneralName, "OtherName"),
275                    )),
276                    (TagClass::ContextSpecific, Encoding::Primitive, 1) => Ok(GeneralName::Rfc822Name(
277                        seq_next_element!(seq, ImplicitContextTag1<Ia5StringAsn1>, GeneralName, "RFC822Name").0,
278                    )),
279                    (TagClass::ContextSpecific, Encoding::Constructed, 1) => Ok(GeneralName::Rfc822Name(
280                        seq_next_element!(seq, ExplicitContextTag1<Ia5StringAsn1>, GeneralName, "RFC822Name").0,
281                    )),
282                    (TagClass::ContextSpecific, Encoding::Primitive, 2) => Ok(GeneralName::DnsName(
283                        seq_next_element!(seq, ImplicitContextTag2<Ia5StringAsn1>, GeneralName, "DNSName").0,
284                    )),
285                    (TagClass::ContextSpecific, Encoding::Constructed, 2) => Ok(GeneralName::DnsName(
286                        seq_next_element!(seq, ExplicitContextTag2<Ia5StringAsn1>, GeneralName, "DNSName").0,
287                    )),
288                    (TagClass::ContextSpecific, _, 3) => Err(serde_invalid_value!(
289                        GeneralName,
290                        "X400Address not supported",
291                        "a supported choice"
292                    )),
293                    (TagClass::ContextSpecific, Encoding::Primitive, 4) => Ok(GeneralName::DirectoryName(
294                        seq_next_element!(seq, ImplicitContextTag4<Name>, GeneralName, "DirectoryName").0,
295                    )),
296                    (TagClass::ContextSpecific, Encoding::Constructed, 4) => Ok(GeneralName::DirectoryName(
297                        seq_next_element!(seq, ExplicitContextTag4<Name>, GeneralName, "DirectoryName").0,
298                    )),
299                    (TagClass::ContextSpecific, Encoding::Primitive, 5) => Ok(GeneralName::EdiPartyName(
300                        seq_next_element!(seq, ImplicitContextTag5<EdiPartyName>, GeneralName, "EDIPartyName").0,
301                    )),
302                    (TagClass::ContextSpecific, Encoding::Constructed, 5) => Ok(GeneralName::EdiPartyName(
303                        seq_next_element!(seq, ExplicitContextTag5<EdiPartyName>, GeneralName, "EDIPartyName").0,
304                    )),
305                    (TagClass::ContextSpecific, Encoding::Primitive, 6) => Ok(GeneralName::Uri(
306                        seq_next_element!(seq, ImplicitContextTag6<Ia5StringAsn1>, GeneralName, "URI").0,
307                    )),
308                    (TagClass::ContextSpecific, Encoding::Constructed, 6) => Ok(GeneralName::Uri(
309                        seq_next_element!(seq, ExplicitContextTag6<Ia5StringAsn1>, GeneralName, "URI").0,
310                    )),
311                    (TagClass::ContextSpecific, Encoding::Primitive, 7) => Ok(GeneralName::IpAddress(
312                        seq_next_element!(seq, ImplicitContextTag7<OctetStringAsn1>, GeneralName, "IpAddress").0,
313                    )),
314                    (TagClass::ContextSpecific, Encoding::Constructed, 7) => Ok(GeneralName::IpAddress(
315                        seq_next_element!(seq, ExplicitContextTag7<OctetStringAsn1>, GeneralName, "IpAddress").0,
316                    )),
317                    (TagClass::ContextSpecific, Encoding::Primitive, 8) => Ok(GeneralName::RegisteredId(
318                        seq_next_element!(
319                            seq,
320                            ImplicitContextTag8<ObjectIdentifierAsn1>,
321                            GeneralName,
322                            "RegisteredId"
323                        )
324                        .0,
325                    )),
326                    (TagClass::ContextSpecific, Encoding::Constructed, 8) => Ok(GeneralName::RegisteredId(
327                        seq_next_element!(
328                            seq,
329                            ExplicitContextTag8<ObjectIdentifierAsn1>,
330                            GeneralName,
331                            "RegisteredId"
332                        )
333                        .0,
334                    )),
335                    _ => Err(serde_invalid_value!(
336                        GeneralName,
337                        "unknown choice value",
338                        "a supported GeneralName choice"
339                    )),
340                }
341            }
342        }
343
344        deserializer.deserialize_enum(
345            "GeneralName",
346            &[
347                "RFC822Name",
348                "DNSName",
349                "DirectoryName",
350                "EDIPartyName",
351                "URI",
352                "IpAddress",
353                "RegisteredId",
354            ],
355            Visitor,
356        )
357    }
358}
359
360// OtherName ::= SEQUENCE {
361//      type-id    OBJECT IDENTIFIER,
362//      value      [0] EXPLICIT ANY DEFINED BY type-id}
363#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
364pub struct OtherName {
365    pub type_id: ObjectIdentifierAsn1,
366    pub value: ExplicitContextTag0<picky_asn1_der::Asn1RawDer>,
367}
368
369/// [RFC 5280 #4.2.1.6](https://tools.ietf.org/html/rfc5280#section-4.2.1.6)
370///
371/// ```not_rust
372/// EDIPartyName ::= SEQUENCE {
373///      nameAssigner            [0]     DirectoryString OPTIONAL,
374///      partyName               [1]     DirectoryString }
375/// ```
376#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
377pub struct EdiPartyName {
378    pub name_assigner: Optional<Option<ImplicitContextTag0<DirectoryString>>>,
379    pub party_name: ImplicitContextTag1<DirectoryString>,
380}
381
382#[cfg(test)]
383mod tests {
384    use super::*;
385    use base64::{engine::general_purpose, Engine as _};
386    use oid::ObjectIdentifier;
387    use picky_asn1::restricted_string::Ia5String;
388    use picky_asn1_der::Asn1RawDer;
389    use std::str::FromStr;
390
391    #[test]
392    fn common_name() {
393        #[rustfmt::skip]
394        let encoded = [
395            0x30, 0x1D, // sequence
396            0x31, 0x1B, // set
397            0x30, 0x19, // sequence
398            0x06, 0x03, // tag of oid
399            0x55, 0x04, 0x03, // oid of common name
400            0x0c, 0x12,  // tag of utf-8 string
401            0x74, 0x65, 0x73, 0x74, 0x2E, 0x63, 0x6F, 0x6E, 0x74, 0x6F,
402            0x73, 0x6F, 0x2E, 0x6C, 0x6F, 0x63, 0x61, 0x6C, // utf8 string
403        ];
404        let expected = Name::new_common_name("test.contoso.local");
405        check_serde!(expected: Name in encoded);
406    }
407
408    #[test]
409    fn multiple_attributes() {
410        #[rustfmt::skip]
411        let encoded = [
412            0x30, 0x52, // sequence, 0x52(82) bytes
413            0x31, 0x1B, // set 1 (common name), 0x1b(27) bytes
414            0x30, 0x19, // sequence, 0x19(25) bytes
415            0x06, 0x03, // oid tag
416            0x55, 0x04, 0x03, // oid of common name attribute
417            0x0c, 0x12,  // tag of utf-8 string
418            b't', b'e', b's', b't', b'.', b'c', b'o', b'n', b't', b'o', b's', b'o', b'.', b'l', b'o', b'c', b'a', b'l',
419
420            0x31, 0x10, // set 2 (locality)
421            0x30, 0x0E, // sequence
422            0x06, 0x03, //oid tag
423            0x55, 0x04, 0x07, // oid of locality attribute
424            0x0c, 0x07,  // tag of utf-8 string
425            b'U', b'n', b'k', b'n', b'o', b'w', b'n', // utf8 string data
426
427            0x31, 0x21, // set 3 (emailAddress)
428            0x30, 0x1F, // sequence
429            0x06, 0x09, // oid tag
430            0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x01, // oid of emailAddress
431            0x16, 0x12,  // tag of IA5String
432            b's', b'o', b'm', b'e', b'@', b'c', b'o', b'n', b't', b'o', b's', b'o', b'.', b'l', b'o', b'c', b'a', b'l', // utf-8 string data
433            ];
434        let mut expected = Name::new_common_name("test.contoso.local");
435        expected.add_attr(NameAttr::LocalityName, "Unknown");
436        let email = Ia5StringAsn1(Ia5String::from_str("some@contoso.local").unwrap());
437        expected.add_email(email);
438        check_serde!(expected: Name in encoded);
439    }
440
441    #[test]
442    fn general_name_dns() {
443        #[rustfmt::skip]
444        let encoded = [
445            0x82, 0x11,
446            0x64, 0x65, 0x76, 0x65, 0x6C, 0x2E, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x2E, 0x63, 0x6F, 0x6D,
447        ];
448        let expected = GeneralName::DnsName(Ia5String::from_string("devel.example.com".into()).unwrap().into());
449        check_serde!(expected: GeneralName in encoded);
450    }
451
452    #[test]
453    fn general_name_other_name() {
454        let encoded = [
455            160, 24, 6, 8, 43, 6, 1, 5, 5, 7, 8, 3, 160, 12, 48, 10, 12, 8, 65, 69, 45, 57, 52, 51, 52, 57,
456        ];
457
458        let expected = GeneralName::OtherName(OtherName {
459            type_id: ObjectIdentifierAsn1(ObjectIdentifier::try_from("1.3.6.1.5.5.7.8.3").unwrap()),
460            value: ExplicitContextTag0::from(Asn1RawDer(vec![48, 10, 12, 8, 65, 69, 45, 57, 52, 51, 52, 57])),
461        });
462
463        check_serde!(expected: GeneralName in encoded);
464    }
465}