1 use oid::ObjectIdentifier;
2 use picky_asn1::restricted_string::{CharSetError, IA5String};
3 use picky_asn1::wrapper::{Asn1SequenceOf, IA5StringAsn1};
4 use picky_asn1_x509::{
5     DirectoryString, GeneralName as SerdeGeneralName, GeneralNames as SerdeGeneralNames, Name, NamePrettyFormatter,
6 };
7 use std::fmt;
8 
9 // === DirectoryName ===
10 
11 pub use picky_asn1_x509::NameAttr;
12 
13 #[derive(Clone, Debug, PartialEq)]
14 pub struct DirectoryName(Name);
15 
16 impl Default for DirectoryName {
default() -> Self17     fn default() -> Self {
18         Self::new()
19     }
20 }
21 
22 impl DirectoryName {
new() -> Self23     pub fn new() -> Self {
24         Self(Name::new())
25     }
26 
new_common_name<S: Into<DirectoryString>>(name: S) -> Self27     pub fn new_common_name<S: Into<DirectoryString>>(name: S) -> Self {
28         Self(Name::new_common_name(name))
29     }
30 
31     /// Find the first common name contained in this `Name`
find_common_name(&self) -> Option<&DirectoryString>32     pub fn find_common_name(&self) -> Option<&DirectoryString> {
33         self.0.find_common_name()
34     }
35 
add_attr<S: Into<DirectoryString>>(&mut self, attr: NameAttr, value: S)36     pub fn add_attr<S: Into<DirectoryString>>(&mut self, attr: NameAttr, value: S) {
37         self.0.add_attr(attr, value)
38     }
39 
40     /// Add an emailAddress attribute.
41     /// 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)42     pub fn add_email<S: Into<IA5StringAsn1>>(&mut self, value: S) {
43         self.0.add_email(value)
44     }
45 }
46 
47 impl fmt::Display for DirectoryName {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result48     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49         NamePrettyFormatter(&self.0).fmt(f)
50     }
51 }
52 
53 impl From<Name> for DirectoryName {
from(name: Name) -> Self54     fn from(name: Name) -> Self {
55         Self(name)
56     }
57 }
58 
59 impl From<DirectoryName> for Name {
from(name: DirectoryName) -> Self60     fn from(name: DirectoryName) -> Self {
61         name.0
62     }
63 }
64 
65 // === GeneralNames === //
66 
67 #[derive(Debug, PartialEq, Clone)]
68 pub enum GeneralName {
69     RFC822Name(IA5String),
70     DNSName(IA5String),
71     DirectoryName(DirectoryName),
72     EDIPartyName {
73         name_assigner: Option<DirectoryString>,
74         party_name: DirectoryString,
75     },
76     URI(IA5String),
77     IpAddress(Vec<u8>),
78     RegisteredId(ObjectIdentifier),
79 }
80 
81 impl GeneralName {
new_rfc822_name<S: Into<String>>(name: S) -> Result<Self, CharSetError>82     pub fn new_rfc822_name<S: Into<String>>(name: S) -> Result<Self, CharSetError> {
83         Ok(Self::RFC822Name(IA5String::from_string(name.into())?))
84     }
85 
new_dns_name<S: Into<String>>(name: S) -> Result<Self, CharSetError>86     pub fn new_dns_name<S: Into<String>>(name: S) -> Result<Self, CharSetError> {
87         Ok(Self::DNSName(IA5String::from_string(name.into())?))
88     }
89 
new_directory_name<N: Into<DirectoryName>>(name: N) -> Self90     pub fn new_directory_name<N: Into<DirectoryName>>(name: N) -> Self {
91         Self::DirectoryName(name.into())
92     }
93 
new_edi_party_name<PN, NA>(party_name: PN, name_assigner: Option<NA>) -> Self where PN: Into<DirectoryString>, NA: Into<DirectoryString>,94     pub fn new_edi_party_name<PN, NA>(party_name: PN, name_assigner: Option<NA>) -> Self
95     where
96         PN: Into<DirectoryString>,
97         NA: Into<DirectoryString>,
98     {
99         Self::EDIPartyName {
100             name_assigner: name_assigner.map(Into::into),
101             party_name: party_name.into(),
102         }
103     }
104 
new_uri<S: Into<String>>(uri: S) -> Result<Self, CharSetError>105     pub fn new_uri<S: Into<String>>(uri: S) -> Result<Self, CharSetError> {
106         Ok(Self::URI(IA5String::from_string(uri.into())?))
107     }
108 
new_ip_address<ADDR: Into<Vec<u8>>>(ip_address: ADDR) -> Self109     pub fn new_ip_address<ADDR: Into<Vec<u8>>>(ip_address: ADDR) -> Self {
110         Self::IpAddress(ip_address.into())
111     }
112 
new_registered_id<OID: Into<ObjectIdentifier>>(oid: OID) -> Self113     pub fn new_registered_id<OID: Into<ObjectIdentifier>>(oid: OID) -> Self {
114         Self::RegisteredId(oid.into())
115     }
116 }
117 
118 impl From<SerdeGeneralName> for GeneralName {
from(gn: SerdeGeneralName) -> Self119     fn from(gn: SerdeGeneralName) -> Self {
120         match gn {
121             SerdeGeneralName::RFC822Name(name) => Self::RFC822Name(name.0),
122             SerdeGeneralName::DNSName(name) => Self::DNSName(name.0),
123             SerdeGeneralName::DirectoryName(name) => Self::DirectoryName(name.into()),
124             SerdeGeneralName::EDIPartyName(edi_pn) => Self::EDIPartyName {
125                 name_assigner: edi_pn.name_assigner.0.map(|na| na.0),
126                 party_name: edi_pn.party_name.0,
127             },
128             SerdeGeneralName::URI(uri) => Self::URI(uri.0),
129             SerdeGeneralName::IpAddress(ip_addr) => Self::IpAddress(ip_addr.0),
130             SerdeGeneralName::RegisteredId(id) => Self::RegisteredId(id.0),
131         }
132     }
133 }
134 
135 impl From<GeneralName> for SerdeGeneralName {
from(gn: GeneralName) -> Self136     fn from(gn: GeneralName) -> Self {
137         match gn {
138             GeneralName::RFC822Name(name) => SerdeGeneralName::RFC822Name(name.into()),
139             GeneralName::DNSName(name) => SerdeGeneralName::DNSName(name.into()),
140             GeneralName::DirectoryName(name) => SerdeGeneralName::DirectoryName(name.into()),
141             GeneralName::EDIPartyName {
142                 name_assigner,
143                 party_name,
144             } => SerdeGeneralName::new_edi_party_name(party_name, name_assigner),
145             GeneralName::URI(uri) => SerdeGeneralName::URI(uri.into()),
146             GeneralName::IpAddress(ip_addr) => SerdeGeneralName::IpAddress(ip_addr.into()),
147             GeneralName::RegisteredId(id) => SerdeGeneralName::RegisteredId(id.into()),
148         }
149     }
150 }
151 
152 impl From<GeneralName> for SerdeGeneralNames {
from(gn: GeneralName) -> Self153     fn from(gn: GeneralName) -> Self {
154         GeneralNames::new(gn).into()
155     }
156 }
157 
158 /// Wraps x509 `GeneralNames` into an easy to use API.
159 ///
160 /// # Example
161 ///
162 /// ```
163 /// use picky::x509::name::{GeneralNames, GeneralName, DirectoryName};
164 ///
165 /// let common_name = GeneralName::new_directory_name(DirectoryName::new_common_name("MyName"));
166 /// let dns_name = GeneralName::new_dns_name("localhost").expect("invalid name string");
167 /// let names = GeneralNames::from(vec![common_name, dns_name]);
168 /// ```
169 #[derive(Clone, Debug, PartialEq)]
170 pub struct GeneralNames(SerdeGeneralNames);
171 
172 impl GeneralNames {
173     /// # Example
174     ///
175     /// ```
176     /// use picky::x509::name::{GeneralName, GeneralNames};
177     ///
178     /// let dns_name = GeneralName::new_dns_name("localhost").expect("invalid name string");
179     /// let names = GeneralNames::new(dns_name);
180     /// ```
new<GN: Into<GeneralName>>(gn: GN) -> Self181     pub fn new<GN: Into<GeneralName>>(gn: GN) -> Self {
182         let gn = gn.into();
183         Self(Asn1SequenceOf(vec![gn.into()]))
184     }
185 
new_directory_name<DN: Into<DirectoryName>>(name: DN) -> Self186     pub fn new_directory_name<DN: Into<DirectoryName>>(name: DN) -> Self {
187         let gn = GeneralName::new_directory_name(name);
188         Self::new(gn)
189     }
190 
with_directory_name<DN: Into<DirectoryName>>(mut self, name: DN) -> Self191     pub fn with_directory_name<DN: Into<DirectoryName>>(mut self, name: DN) -> Self {
192         let gn = GeneralName::new_directory_name(name);
193         (self.0).0.push(gn.into());
194         self
195     }
196 
find_directory_name(&self) -> Option<DirectoryName>197     pub fn find_directory_name(&self) -> Option<DirectoryName> {
198         for name in &(self.0).0 {
199             if let SerdeGeneralName::DirectoryName(name) = name {
200                 return Some(name.clone().into());
201             }
202         }
203         None
204     }
205 
206     /// # Example
207     ///
208     /// ```
209     /// use picky::x509::name::GeneralNames;
210     /// use picky_asn1::restricted_string::IA5String;
211     ///
212     /// let names = GeneralNames::new_dns_name(IA5String::new("localhost").unwrap());
213     /// ```
new_dns_name<IA5: Into<IA5String>>(dns_name: IA5) -> Self214     pub fn new_dns_name<IA5: Into<IA5String>>(dns_name: IA5) -> Self {
215         let gn = GeneralName::DNSName(dns_name.into());
216         Self::new(gn)
217     }
218 
219     /// # Example
220     ///
221     /// ```
222     /// use picky::x509::name::{GeneralNames, DirectoryName};
223     /// use picky_asn1::restricted_string::IA5String;
224     ///
225     /// let names = GeneralNames::new_directory_name(DirectoryName::new_common_name("MyName"))
226     ///         .with_dns_name(IA5String::new("localhost").unwrap());
227     /// ```
with_dns_name<IA5: Into<IA5String>>(mut self, dns_name: IA5) -> Self228     pub fn with_dns_name<IA5: Into<IA5String>>(mut self, dns_name: IA5) -> Self {
229         let gn = GeneralName::DNSName(dns_name.into());
230         (self.0).0.push(gn.into());
231         self
232     }
233 
find_dns_name(&self) -> Option<&IA5String>234     pub fn find_dns_name(&self) -> Option<&IA5String> {
235         for name in &(self.0).0 {
236             if let SerdeGeneralName::DNSName(name) = name {
237                 return Some(&name.0);
238             }
239         }
240         None
241     }
242 
add_name<GN: Into<GeneralName>>(&mut self, name: GN)243     pub fn add_name<GN: Into<GeneralName>>(&mut self, name: GN) {
244         let gn = name.into();
245         (self.0).0.push(gn.into());
246     }
247 
248     /// # Example
249     ///
250     /// ```
251     /// use picky::x509::name::{GeneralNames, GeneralName, DirectoryName};
252     ///
253     /// let common_name = GeneralName::new_directory_name(DirectoryName::new_common_name("MyName"));
254     /// let dns_name = GeneralName::new_dns_name("localhost").expect("invalid name string");
255     /// let names = GeneralNames::new(common_name).with_name(dns_name);
256     /// ```
with_name<GN: Into<GeneralName>>(mut self, name: GN) -> Self257     pub fn with_name<GN: Into<GeneralName>>(mut self, name: GN) -> Self {
258         let gn = name.into();
259         (self.0).0.push(gn.into());
260         self
261     }
262 
into_general_names(self) -> Vec<GeneralName>263     pub fn into_general_names(self) -> Vec<GeneralName> {
264         (self.0).0.into_iter().map(|gn| gn.into()).collect()
265     }
266 
to_general_names(&self) -> Vec<GeneralName>267     pub fn to_general_names(&self) -> Vec<GeneralName> {
268         (self.0).0.iter().map(|gn| gn.clone().into()).collect()
269     }
270 }
271 
272 impl From<SerdeGeneralNames> for GeneralNames {
from(gn: SerdeGeneralNames) -> Self273     fn from(gn: SerdeGeneralNames) -> Self {
274         Self(gn)
275     }
276 }
277 
278 impl From<GeneralNames> for SerdeGeneralNames {
from(gn: GeneralNames) -> Self279     fn from(gn: GeneralNames) -> Self {
280         gn.0
281     }
282 }
283 
284 impl From<Vec<GeneralName>> for GeneralNames {
from(names: Vec<GeneralName>) -> Self285     fn from(names: Vec<GeneralName>) -> Self {
286         let serde_names = names.into_iter().map(|n| n.into()).collect();
287         Self(Asn1SequenceOf(serde_names))
288     }
289 }
290 
291 #[cfg(test)]
292 mod tests {
293     use super::*;
294 
295     #[test]
build_and_format_directory_name()296     fn build_and_format_directory_name() {
297         let mut my_name = DirectoryName::new_common_name("CommonName");
298         my_name.add_attr(NameAttr::StateOrProvinceName, "SomeState");
299         my_name.add_attr(NameAttr::CountryName, "SomeCountry");
300         assert_eq!(my_name.to_string(), "CN=CommonName,ST=SomeState,C=SomeCountry");
301     }
302 
303     #[test]
find_common_name()304     fn find_common_name() {
305         let my_name = DirectoryName::new_common_name("CommonName");
306         let cn = my_name.find_common_name().unwrap();
307         assert_eq!(cn.to_utf8_lossy(), "CommonName");
308     }
309 }
310