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
22pub type RdnSequence = Asn1SequenceOf<RelativeDistinguishedName>;
28
29pub type RelativeDistinguishedName = Asn1SetOf<AttributeTypeAndValue>;
35
36pub type DirectoryName = Name;
42
43#[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 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 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
173pub type GeneralNames = Asn1SequenceOf<GeneralName>;
179
180#[derive(Debug, PartialEq, Eq, Clone)]
195pub enum GeneralName {
196 OtherName(OtherName),
197 Rfc822Name(Ia5StringAsn1),
198 DnsName(Ia5StringAsn1),
199 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#[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#[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, 0x31, 0x1B, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x12, 0x74, 0x65, 0x73, 0x74, 0x2E, 0x63, 0x6F, 0x6E, 0x74, 0x6F,
402 0x73, 0x6F, 0x2E, 0x6C, 0x6F, 0x63, 0x61, 0x6C, ];
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, 0x31, 0x1B, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x12, 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, 0x30, 0x0E, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x07, b'U', b'n', b'k', b'n', b'o', b'w', b'n', 0x31, 0x21, 0x30, 0x1F, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x01, 0x16, 0x12, 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', ];
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}