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