1 use crate::{AttributeTypeAndValue, AttributeTypeAndValueParameters, DirectoryString};
2 use picky_asn1::tag::{Tag, TagPeeker};
3 use picky_asn1::wrapper::{
4     ApplicationTag1, ApplicationTag2, ApplicationTag4, ApplicationTag5, ApplicationTag6, ApplicationTag7,
5     ApplicationTag8, Asn1SequenceOf, Asn1SetOf, ContextTag0, ContextTag1, ContextTag2, ContextTag4, ContextTag5,
6     ContextTag6, ContextTag7, ContextTag8, IA5StringAsn1, Implicit, ObjectIdentifierAsn1, OctetStringAsn1,
7 };
8 use serde::{de, ser, Deserialize, Serialize};
9 use std::fmt;
10 
11 #[derive(Clone, Debug, PartialEq)]
12 pub enum NameAttr {
13     CommonName,
14     Surname,
15     SerialNumber,
16     CountryName,
17     LocalityName,
18     StateOrProvinceName,
19     StreetName,
20     OrganizationName,
21     OrganizationalUnitName,
22 }
23 
24 /// [RFC 5280 #4.1.2.4](https://tools.ietf.org/html/rfc5280#section-4.1.2.4)
25 ///
26 /// ```not_rust
27 /// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
28 /// ```
29 pub type RDNSequence = Asn1SequenceOf<RelativeDistinguishedName>;
30 
31 /// [RFC 5280 #4.1.2.4](https://tools.ietf.org/html/rfc5280#section-4.1.2.4)
32 ///
33 /// ```not_rust
34 /// RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue
35 /// ```
36 pub type RelativeDistinguishedName = Asn1SetOf<AttributeTypeAndValue>;
37 
38 /// [RFC 5280 #4.2.1.6](https://tools.ietf.org/html/rfc5280#section-4.2.1.6)
39 ///
40 /// ```not_rust
41 /// DirectoryName ::= Name
42 /// ```
43 pub type DirectoryName = Name;
44 
45 /// [RFC 5280 #4.1.2.4](https://tools.ietf.org/html/rfc5280#section-4.1.2.4)
46 ///
47 /// ```not_rust
48 /// Name ::= CHOICE { -- only one possibility for now --
49 ///       rdnSequence  RDNSequence }
50 /// ```
51 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
52 pub struct Name(pub RDNSequence);
53 
54 impl Default for Name {
default() -> Self55     fn default() -> Self {
56         Self::new()
57     }
58 }
59 
60 impl Name {
new() -> Self61     pub fn new() -> Self {
62         Self(Asn1SequenceOf(Vec::new()))
63     }
64 
new_common_name<S: Into<DirectoryString>>(name: S) -> Self65     pub fn new_common_name<S: Into<DirectoryString>>(name: S) -> Self {
66         let mut dn = Self::default();
67         dn.add_attr(NameAttr::CommonName, name);
68         dn
69     }
70 
71     /// Find the first common name contained in this `Name`
find_common_name(&self) -> Option<&DirectoryString>72     pub fn find_common_name(&self) -> Option<&DirectoryString> {
73         for relative_distinguished_name in &((self.0).0) {
74             for attr_ty_val in &relative_distinguished_name.0 {
75                 if let AttributeTypeAndValueParameters::CommonName(dir_string) = &attr_ty_val.value {
76                     return Some(dir_string);
77                 }
78             }
79         }
80         None
81     }
82 
add_attr<S: Into<DirectoryString>>(&mut self, attr: NameAttr, value: S)83     pub fn add_attr<S: Into<DirectoryString>>(&mut self, attr: NameAttr, value: S) {
84         let ty_val = match attr {
85             NameAttr::CommonName => AttributeTypeAndValue::new_common_name(value),
86             NameAttr::Surname => AttributeTypeAndValue::new_surname(value),
87             NameAttr::SerialNumber => AttributeTypeAndValue::new_serial_number(value),
88             NameAttr::CountryName => AttributeTypeAndValue::new_country_name(value),
89             NameAttr::LocalityName => AttributeTypeAndValue::new_locality_name(value),
90             NameAttr::StateOrProvinceName => AttributeTypeAndValue::new_state_or_province_name(value),
91             NameAttr::StreetName => AttributeTypeAndValue::new_street_name(value),
92             NameAttr::OrganizationName => AttributeTypeAndValue::new_organization_name(value),
93             NameAttr::OrganizationalUnitName => AttributeTypeAndValue::new_organizational_unit_name(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
add_email<S: Into<IA5StringAsn1>>(&mut self, value: S)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 
107 impl fmt::Display for Name {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result108     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109         NamePrettyFormatter(self).fmt(f)
110     }
111 }
112 
113 pub struct NamePrettyFormatter<'a>(pub &'a Name);
114 
115 impl fmt::Display for NamePrettyFormatter<'_> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result116     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::Custom(der) => {
158                         write!(f, "{}={:?}", Into::<String>::into(&attr.ty.0), der)?;
159                     }
160                 }
161             }
162         }
163         Ok(())
164     }
165 }
166 
167 /// [RFC 5280 #4.2.1.6](https://tools.ietf.org/html/rfc5280#section-4.2.1.6)
168 ///
169 /// ```not_rust
170 /// GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
171 /// ```
172 pub type GeneralNames = Asn1SequenceOf<GeneralName>;
173 
174 /// [RFC 5280 #4.2.1.6](https://tools.ietf.org/html/rfc5280#section-4.2.1.6)
175 ///
176 /// ```not_rust
177 /// GeneralName ::= CHOICE {
178 ///       otherName                       [0]     OtherName,
179 ///       rfc822Name                      [1]     IA5String,
180 ///       dNSName                         [2]     IA5String,
181 ///       x400Address                     [3]     ORAddress,
182 ///       directoryName                   [4]     Name,
183 ///       ediPartyName                    [5]     EDIPartyName,
184 ///       uniformResourceIdentifier       [6]     IA5String,
185 ///       iPAddress                       [7]     OCTET STRING,
186 ///       registeredID                    [8]     OBJECT IDENTIFIER }
187 /// ```
188 #[derive(Debug, PartialEq, Clone)]
189 pub enum GeneralName {
190     //OtherName(OtherName),
191     RFC822Name(IA5StringAsn1),
192     DNSName(IA5StringAsn1),
193     //X400Address(ORAddress),
194     DirectoryName(Name),
195     EDIPartyName(EDIPartyName),
196     URI(IA5StringAsn1),
197     IpAddress(OctetStringAsn1),
198     RegisteredId(ObjectIdentifierAsn1),
199 }
200 
201 impl GeneralName {
new_edi_party_name<PN, NA>(party_name: PN, name_assigner: Option<NA>) -> Self where PN: Into<DirectoryString>, NA: Into<DirectoryString>,202     pub fn new_edi_party_name<PN, NA>(party_name: PN, name_assigner: Option<NA>) -> Self
203     where
204         PN: Into<DirectoryString>,
205         NA: Into<DirectoryString>,
206     {
207         Self::EDIPartyName(EDIPartyName {
208             name_assigner: Implicit(name_assigner.map(Into::into).map(ContextTag0)),
209             party_name: ContextTag1(party_name.into()),
210         })
211     }
212 }
213 
214 impl From<Name> for GeneralName {
from(name: Name) -> Self215     fn from(name: Name) -> Self {
216         Self::DirectoryName(name)
217     }
218 }
219 
220 impl ser::Serialize for GeneralName {
serialize<S>(&self, serializer: S) -> Result<<S as ser::Serializer>::Ok, <S as ser::Serializer>::Error> where S: ser::Serializer,221     fn serialize<S>(&self, serializer: S) -> Result<<S as ser::Serializer>::Ok, <S as ser::Serializer>::Error>
222     where
223         S: ser::Serializer,
224     {
225         match &self {
226             GeneralName::RFC822Name(name) => ContextTag1(name).serialize(serializer),
227             GeneralName::DNSName(name) => ContextTag2(name).serialize(serializer),
228             GeneralName::DirectoryName(name) => ContextTag4(name).serialize(serializer),
229             GeneralName::EDIPartyName(name) => ContextTag5(name).serialize(serializer),
230             GeneralName::URI(name) => ContextTag6(name).serialize(serializer),
231             GeneralName::IpAddress(name) => ContextTag7(name).serialize(serializer),
232             GeneralName::RegisteredId(name) => ContextTag8(name).serialize(serializer),
233         }
234     }
235 }
236 
237 impl<'de> de::Deserialize<'de> for GeneralName {
deserialize<D>(deserializer: D) -> Result<Self, <D as de::Deserializer<'de>>::Error> where D: de::Deserializer<'de>,238     fn deserialize<D>(deserializer: D) -> Result<Self, <D as de::Deserializer<'de>>::Error>
239     where
240         D: de::Deserializer<'de>,
241     {
242         struct Visitor;
243 
244         impl<'de> de::Visitor<'de> for Visitor {
245             type Value = GeneralName;
246 
247             fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
248                 formatter.write_str("a valid DER-encoded GeneralName")
249             }
250 
251             fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
252             where
253                 A: de::SeqAccess<'de>,
254             {
255                 let tag_peeker: TagPeeker = seq_next_element!(seq, DirectoryString, "choice tag");
256                 match tag_peeker.next_tag {
257                     Tag::CTX_0 | Tag::APP_0 => Err(serde_invalid_value!(
258                         GeneralName,
259                         "OtherName not supported",
260                         "a supported choice"
261                     )),
262                     Tag::CTX_1 => Ok(GeneralName::RFC822Name(
263                         seq_next_element!(seq, ContextTag1<IA5StringAsn1>, GeneralName, "RFC822Name").0,
264                     )),
265                     Tag::APP_1 => Ok(GeneralName::RFC822Name(
266                         seq_next_element!(seq, ApplicationTag1<IA5StringAsn1>, GeneralName, "RFC822Name").0,
267                     )),
268                     Tag::CTX_2 => Ok(GeneralName::DNSName(
269                         seq_next_element!(seq, ContextTag2<IA5StringAsn1>, GeneralName, "DNSName").0,
270                     )),
271                     Tag::APP_2 => Ok(GeneralName::DNSName(
272                         seq_next_element!(seq, ApplicationTag2<IA5StringAsn1>, GeneralName, "DNSName").0,
273                     )),
274                     Tag::CTX_3 | Tag::APP_3 => Err(serde_invalid_value!(
275                         GeneralName,
276                         "X400Address not supported",
277                         "a supported choice"
278                     )),
279                     Tag::CTX_4 => Ok(GeneralName::DirectoryName(
280                         seq_next_element!(seq, ContextTag4<Name>, GeneralName, "DirectoryName").0,
281                     )),
282                     Tag::APP_4 => Ok(GeneralName::DirectoryName(
283                         seq_next_element!(seq, ApplicationTag4<Name>, GeneralName, "DirectoryName").0,
284                     )),
285                     Tag::CTX_5 => Ok(GeneralName::EDIPartyName(
286                         seq_next_element!(seq, ContextTag5<EDIPartyName>, GeneralName, "EDIPartyName").0,
287                     )),
288                     Tag::APP_5 => Ok(GeneralName::EDIPartyName(
289                         seq_next_element!(seq, ApplicationTag5<EDIPartyName>, GeneralName, "EDIPartyName").0,
290                     )),
291                     Tag::CTX_6 => Ok(GeneralName::URI(
292                         seq_next_element!(seq, ContextTag6<IA5StringAsn1>, GeneralName, "URI").0,
293                     )),
294                     Tag::APP_6 => Ok(GeneralName::URI(
295                         seq_next_element!(seq, ApplicationTag6<IA5StringAsn1>, GeneralName, "URI").0,
296                     )),
297                     Tag::CTX_7 => Ok(GeneralName::IpAddress(
298                         seq_next_element!(seq, ContextTag7<OctetStringAsn1>, GeneralName, "IpAddress").0,
299                     )),
300                     Tag::APP_7 => Ok(GeneralName::IpAddress(
301                         seq_next_element!(seq, ApplicationTag7<OctetStringAsn1>, GeneralName, "IpAddress").0,
302                     )),
303                     Tag::CTX_8 => Ok(GeneralName::RegisteredId(
304                         seq_next_element!(seq, ContextTag8<ObjectIdentifierAsn1>, GeneralName, "RegisteredId").0,
305                     )),
306                     Tag::APP_8 => Ok(GeneralName::RegisteredId(
307                         seq_next_element!(seq, ApplicationTag8<ObjectIdentifierAsn1>, GeneralName, "RegisteredId").0,
308                     )),
309                     _ => Err(serde_invalid_value!(
310                         GeneralName,
311                         "unknown choice value",
312                         "a supported GeneralName choice"
313                     )),
314                 }
315             }
316         }
317 
318         deserializer.deserialize_enum(
319             "GeneralName",
320             &[
321                 "RFC822Name",
322                 "DNSName",
323                 "DirectoryName",
324                 "EDIPartyName",
325                 "URI",
326                 "IpAddress",
327                 "RegisteredId",
328             ],
329             Visitor,
330         )
331     }
332 }
333 
334 // OtherName ::= SEQUENCE {
335 //      type-id    OBJECT IDENTIFIER,
336 //      value      [0] EXPLICIT ANY DEFINED BY type-id }
337 //pub struct OtherName { ... }
338 
339 /// [RFC 5280 #4.2.1.6](https://tools.ietf.org/html/rfc5280#section-4.2.1.6)
340 ///
341 /// ```not_rust
342 /// EDIPartyName ::= SEQUENCE {
343 ///      nameAssigner            [0]     DirectoryString OPTIONAL,
344 ///      partyName               [1]     DirectoryString }
345 /// ```
346 #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
347 pub struct EDIPartyName {
348     pub name_assigner: Implicit<Option<ContextTag0<DirectoryString>>>,
349     pub party_name: ContextTag1<DirectoryString>,
350 }
351 
352 #[cfg(test)]
353 mod tests {
354     use super::*;
355     use picky_asn1::restricted_string::IA5String;
356     use std::str::FromStr;
357 
358     #[test]
common_name()359     fn common_name() {
360         #[rustfmt::skip]
361         let encoded = [
362             0x30, 0x1D, // sequence
363             0x31, 0x1B, // set
364             0x30, 0x19, // sequence
365             0x06, 0x03, // tag of oid
366             0x55, 0x04, 0x03, // oid of common name
367             0x0c, 0x12,  // tag of utf-8 string
368             0x74, 0x65, 0x73, 0x74, 0x2E, 0x63, 0x6F, 0x6E, 0x74, 0x6F,
369             0x73, 0x6F, 0x2E, 0x6C, 0x6F, 0x63, 0x61, 0x6C, // utf8 string
370         ];
371         let expected = Name::new_common_name("test.contoso.local");
372         check_serde!(expected: Name in encoded);
373     }
374 
375     #[test]
multiple_attributes()376     fn multiple_attributes() {
377         #[rustfmt::skip]
378         let encoded = [
379             0x30, 0x52, // sequence, 0x52(82) bytes
380             0x31, 0x1B, // set 1 (common name), 0x1b(27) bytes
381             0x30, 0x19, // sequence, 0x19(25) bytes
382             0x06, 0x03, // oid tag
383             0x55, 0x04, 0x03, // oid of common name attribute
384             0x0c, 0x12,  // tag of utf-8 string
385             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',
386 
387             0x31, 0x10, // set 2 (locality)
388             0x30, 0x0E, // sequence
389             0x06, 0x03, //oid tag
390             0x55, 0x04, 0x07, // oid of locality attribute
391             0x0c, 0x07,  // tag of utf-8 string
392             b'U', b'n', b'k', b'n', b'o', b'w', b'n', // utf8 string data
393 
394             0x31, 0x21, // set 3 (emailAddress)
395             0x30, 0x1F, // sequence
396             0x06, 0x09, // oid tag
397             0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x01, // oid of emailAddress
398             0x16, 0x12,  // tag of IA5String
399             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
400             ];
401         let mut expected = Name::new_common_name("test.contoso.local");
402         expected.add_attr(NameAttr::LocalityName, "Unknown");
403         let email = IA5StringAsn1(IA5String::from_str("some@contoso.local").unwrap());
404         expected.add_email(email);
405         check_serde!(expected: Name in encoded);
406     }
407 
408     #[test]
general_name_dns()409     fn general_name_dns() {
410         #[rustfmt::skip]
411         let encoded = [
412             0x82, 0x11,
413             0x64, 0x65, 0x76, 0x65, 0x6C, 0x2E, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x2E, 0x63, 0x6F, 0x6D,
414         ];
415         let expected = GeneralName::DNSName(IA5String::from_string("devel.example.com".into()).unwrap().into());
416         check_serde!(expected: GeneralName in encoded);
417     }
418 }
419