use crate::{AttributeTypeAndValue, AttributeTypeAndValueParameters, DirectoryString};
use picky_asn1::tag::{Encoding, Tag, TagClass, TagPeeker};
use picky_asn1::wrapper::*;
use serde::{de, ser, Deserialize, Serialize};
use std::fmt;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum NameAttr {
CommonName,
Surname,
SerialNumber,
CountryName,
LocalityName,
StateOrProvinceName,
StreetName,
OrganizationName,
OrganizationalUnitName,
GivenName,
Phone,
}
pub type RdnSequence = Asn1SequenceOf<RelativeDistinguishedName>;
pub type RelativeDistinguishedName = Asn1SetOf<AttributeTypeAndValue>;
pub type DirectoryName = Name;
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct Name(pub RdnSequence);
impl Default for Name {
fn default() -> Self {
Self::new()
}
}
impl Name {
pub fn new() -> Self {
Self(Asn1SequenceOf(Vec::new()))
}
pub fn new_common_name<S: Into<DirectoryString>>(name: S) -> Self {
let mut dn = Self::default();
dn.add_attr(NameAttr::CommonName, name);
dn
}
pub fn find_common_name(&self) -> Option<&DirectoryString> {
for relative_distinguished_name in &((self.0).0) {
for attr_ty_val in &relative_distinguished_name.0 {
if let AttributeTypeAndValueParameters::CommonName(dir_string) = &attr_ty_val.value {
return Some(dir_string);
}
}
}
None
}
pub fn add_attr<S: Into<DirectoryString>>(&mut self, attr: NameAttr, value: S) {
let ty_val = match attr {
NameAttr::CommonName => AttributeTypeAndValue::new_common_name(value),
NameAttr::Surname => AttributeTypeAndValue::new_surname(value),
NameAttr::SerialNumber => AttributeTypeAndValue::new_serial_number(value),
NameAttr::CountryName => AttributeTypeAndValue::new_country_name(value),
NameAttr::LocalityName => AttributeTypeAndValue::new_locality_name(value),
NameAttr::StateOrProvinceName => AttributeTypeAndValue::new_state_or_province_name(value),
NameAttr::StreetName => AttributeTypeAndValue::new_street_name(value),
NameAttr::OrganizationName => AttributeTypeAndValue::new_organization_name(value),
NameAttr::OrganizationalUnitName => AttributeTypeAndValue::new_organizational_unit_name(value),
NameAttr::GivenName => AttributeTypeAndValue::new_given_name(value),
NameAttr::Phone => AttributeTypeAndValue::new_phone(value),
};
let set_val = Asn1SetOf(vec![ty_val]);
((self.0).0).push(set_val);
}
pub fn add_email<S: Into<Ia5StringAsn1>>(&mut self, value: S) {
let set_val = Asn1SetOf(vec![AttributeTypeAndValue::new_email_address(value)]);
((self.0).0).push(set_val);
}
}
impl fmt::Display for Name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
NamePrettyFormatter(self).fmt(f)
}
}
pub struct NamePrettyFormatter<'a>(pub &'a Name);
impl fmt::Display for NamePrettyFormatter<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut first = true;
for name in &((self.0).0).0 {
for attr in &name.0 {
if first {
first = false;
} else {
write!(f, ",")?;
}
match &attr.value {
AttributeTypeAndValueParameters::CommonName(name) => {
write!(f, "CN={}", name)?;
}
AttributeTypeAndValueParameters::Surname(name) => {
write!(f, "SURNAME={}", name)?;
}
AttributeTypeAndValueParameters::SerialNumber(name) => {
write!(f, "SN={}", name)?;
}
AttributeTypeAndValueParameters::CountryName(name) => {
write!(f, "C={}", name)?;
}
AttributeTypeAndValueParameters::LocalityName(name) => {
write!(f, "L={}", name)?;
}
AttributeTypeAndValueParameters::StateOrProvinceName(name) => {
write!(f, "ST={}", name)?;
}
AttributeTypeAndValueParameters::StreetName(name) => {
write!(f, "STREET NAME={}", name)?;
}
AttributeTypeAndValueParameters::OrganizationName(name) => {
write!(f, "O={}", name)?;
}
AttributeTypeAndValueParameters::OrganizationalUnitName(name) => {
write!(f, "OU={}", name)?;
}
AttributeTypeAndValueParameters::EmailAddress(name) => {
write!(f, "EMAIL={}", String::from_utf8_lossy(name.as_bytes()))?;
}
AttributeTypeAndValueParameters::GivenName(name) => {
write!(f, "GIVENNAME={}", name)?;
}
AttributeTypeAndValueParameters::Phone(name) => {
write!(f, "PHONE={}", name)?;
}
AttributeTypeAndValueParameters::Custom(der) => {
write!(f, "{}={:?}", Into::<String>::into(&attr.ty.0), der)?;
}
}
}
}
Ok(())
}
}
pub type GeneralNames = Asn1SequenceOf<GeneralName>;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum GeneralName {
OtherName(OtherName),
Rfc822Name(Ia5StringAsn1),
DnsName(Ia5StringAsn1),
DirectoryName(Name),
EdiPartyName(EdiPartyName),
Uri(Ia5StringAsn1),
IpAddress(OctetStringAsn1),
RegisteredId(ObjectIdentifierAsn1),
}
impl GeneralName {
pub fn new_edi_party_name<PN, NA>(party_name: PN, name_assigner: Option<NA>) -> Self
where
PN: Into<DirectoryString>,
NA: Into<DirectoryString>,
{
Self::EdiPartyName(EdiPartyName {
name_assigner: Optional(name_assigner.map(Into::into).map(ImplicitContextTag0)),
party_name: ImplicitContextTag1(party_name.into()),
})
}
}
impl From<Name> for GeneralName {
fn from(name: Name) -> Self {
Self::DirectoryName(name)
}
}
impl ser::Serialize for GeneralName {
fn serialize<S>(&self, serializer: S) -> Result<<S as ser::Serializer>::Ok, <S as ser::Serializer>::Error>
where
S: ser::Serializer,
{
match &self {
GeneralName::OtherName(name) => {
let mut raw_der = picky_asn1_der::to_vec(name).map_err(ser::Error::custom)?;
raw_der[0] = Tag::context_specific_constructed(0).inner();
picky_asn1_der::Asn1RawDer(raw_der).serialize(serializer)
}
GeneralName::Rfc822Name(name) => ImplicitContextTag1(name).serialize(serializer),
GeneralName::DnsName(name) => ImplicitContextTag2(name).serialize(serializer),
GeneralName::DirectoryName(name) => ImplicitContextTag4(name).serialize(serializer),
GeneralName::EdiPartyName(name) => ImplicitContextTag5(name).serialize(serializer),
GeneralName::Uri(name) => ImplicitContextTag6(name).serialize(serializer),
GeneralName::IpAddress(name) => ImplicitContextTag7(name).serialize(serializer),
GeneralName::RegisteredId(name) => ImplicitContextTag8(name).serialize(serializer),
}
}
}
impl<'de> de::Deserialize<'de> for GeneralName {
fn deserialize<D>(deserializer: D) -> Result<Self, <D as de::Deserializer<'de>>::Error>
where
D: de::Deserializer<'de>,
{
struct Visitor;
impl<'de> de::Visitor<'de> for Visitor {
type Value = GeneralName;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a valid DER-encoded GeneralName")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: de::SeqAccess<'de>,
{
let tag_peeker: TagPeeker = seq_next_element!(seq, DirectoryString, "choice tag");
match tag_peeker.next_tag.components() {
(TagClass::ContextSpecific, Encoding::Primitive, 0) => Err(serde_invalid_value!(
GeneralName,
"Primitive encoding for OtherName not supported",
"a supported choice"
)),
(TagClass::ContextSpecific, Encoding::Constructed, 0) => Ok(GeneralName::OtherName(
seq_next_element!(seq, OtherName, GeneralName, "OtherName"),
)),
(TagClass::ContextSpecific, Encoding::Primitive, 1) => Ok(GeneralName::Rfc822Name(
seq_next_element!(seq, ImplicitContextTag1<Ia5StringAsn1>, GeneralName, "RFC822Name").0,
)),
(TagClass::ContextSpecific, Encoding::Constructed, 1) => Ok(GeneralName::Rfc822Name(
seq_next_element!(seq, ExplicitContextTag1<Ia5StringAsn1>, GeneralName, "RFC822Name").0,
)),
(TagClass::ContextSpecific, Encoding::Primitive, 2) => Ok(GeneralName::DnsName(
seq_next_element!(seq, ImplicitContextTag2<Ia5StringAsn1>, GeneralName, "DNSName").0,
)),
(TagClass::ContextSpecific, Encoding::Constructed, 2) => Ok(GeneralName::DnsName(
seq_next_element!(seq, ExplicitContextTag2<Ia5StringAsn1>, GeneralName, "DNSName").0,
)),
(TagClass::ContextSpecific, _, 3) => Err(serde_invalid_value!(
GeneralName,
"X400Address not supported",
"a supported choice"
)),
(TagClass::ContextSpecific, Encoding::Primitive, 4) => Ok(GeneralName::DirectoryName(
seq_next_element!(seq, ImplicitContextTag4<Name>, GeneralName, "DirectoryName").0,
)),
(TagClass::ContextSpecific, Encoding::Constructed, 4) => Ok(GeneralName::DirectoryName(
seq_next_element!(seq, ExplicitContextTag4<Name>, GeneralName, "DirectoryName").0,
)),
(TagClass::ContextSpecific, Encoding::Primitive, 5) => Ok(GeneralName::EdiPartyName(
seq_next_element!(seq, ImplicitContextTag5<EdiPartyName>, GeneralName, "EDIPartyName").0,
)),
(TagClass::ContextSpecific, Encoding::Constructed, 5) => Ok(GeneralName::EdiPartyName(
seq_next_element!(seq, ExplicitContextTag5<EdiPartyName>, GeneralName, "EDIPartyName").0,
)),
(TagClass::ContextSpecific, Encoding::Primitive, 6) => Ok(GeneralName::Uri(
seq_next_element!(seq, ImplicitContextTag6<Ia5StringAsn1>, GeneralName, "URI").0,
)),
(TagClass::ContextSpecific, Encoding::Constructed, 6) => Ok(GeneralName::Uri(
seq_next_element!(seq, ExplicitContextTag6<Ia5StringAsn1>, GeneralName, "URI").0,
)),
(TagClass::ContextSpecific, Encoding::Primitive, 7) => Ok(GeneralName::IpAddress(
seq_next_element!(seq, ImplicitContextTag7<OctetStringAsn1>, GeneralName, "IpAddress").0,
)),
(TagClass::ContextSpecific, Encoding::Constructed, 7) => Ok(GeneralName::IpAddress(
seq_next_element!(seq, ExplicitContextTag7<OctetStringAsn1>, GeneralName, "IpAddress").0,
)),
(TagClass::ContextSpecific, Encoding::Primitive, 8) => Ok(GeneralName::RegisteredId(
seq_next_element!(
seq,
ImplicitContextTag8<ObjectIdentifierAsn1>,
GeneralName,
"RegisteredId"
)
.0,
)),
(TagClass::ContextSpecific, Encoding::Constructed, 8) => Ok(GeneralName::RegisteredId(
seq_next_element!(
seq,
ExplicitContextTag8<ObjectIdentifierAsn1>,
GeneralName,
"RegisteredId"
)
.0,
)),
_ => Err(serde_invalid_value!(
GeneralName,
"unknown choice value",
"a supported GeneralName choice"
)),
}
}
}
deserializer.deserialize_enum(
"GeneralName",
&[
"RFC822Name",
"DNSName",
"DirectoryName",
"EDIPartyName",
"URI",
"IpAddress",
"RegisteredId",
],
Visitor,
)
}
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
pub struct OtherName {
pub type_id: ObjectIdentifierAsn1,
pub value: ExplicitContextTag0<picky_asn1_der::Asn1RawDer>,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct EdiPartyName {
pub name_assigner: Optional<Option<ImplicitContextTag0<DirectoryString>>>,
pub party_name: ImplicitContextTag1<DirectoryString>,
}
#[cfg(test)]
mod tests {
use super::*;
use base64::{engine::general_purpose, Engine as _};
use oid::ObjectIdentifier;
use picky_asn1::restricted_string::Ia5String;
use picky_asn1_der::Asn1RawDer;
use std::str::FromStr;
#[test]
fn common_name() {
#[rustfmt::skip]
let encoded = [
0x30, 0x1D, 0x31, 0x1B, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x12, 0x74, 0x65, 0x73, 0x74, 0x2E, 0x63, 0x6F, 0x6E, 0x74, 0x6F,
0x73, 0x6F, 0x2E, 0x6C, 0x6F, 0x63, 0x61, 0x6C, ];
let expected = Name::new_common_name("test.contoso.local");
check_serde!(expected: Name in encoded);
}
#[test]
fn multiple_attributes() {
#[rustfmt::skip]
let encoded = [
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',
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', ];
let mut expected = Name::new_common_name("test.contoso.local");
expected.add_attr(NameAttr::LocalityName, "Unknown");
let email = Ia5StringAsn1(Ia5String::from_str("some@contoso.local").unwrap());
expected.add_email(email);
check_serde!(expected: Name in encoded);
}
#[test]
fn general_name_dns() {
#[rustfmt::skip]
let encoded = [
0x82, 0x11,
0x64, 0x65, 0x76, 0x65, 0x6C, 0x2E, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x2E, 0x63, 0x6F, 0x6D,
];
let expected = GeneralName::DnsName(Ia5String::from_string("devel.example.com".into()).unwrap().into());
check_serde!(expected: GeneralName in encoded);
}
#[test]
fn general_name_other_name() {
let encoded = [
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,
];
let expected = GeneralName::OtherName(OtherName {
type_id: ObjectIdentifierAsn1(ObjectIdentifier::try_from("1.3.6.1.5.5.7.8.3").unwrap()),
value: ExplicitContextTag0::from(Asn1RawDer(vec![48, 10, 12, 8, 65, 69, 45, 57, 52, 51, 52, 57])),
});
check_serde!(expected: GeneralName in encoded);
}
}