picky_asn1_x509/
directory_string.rs

1use picky_asn1::restricted_string::{PrintableString, Utf8String};
2use picky_asn1::tag::{Tag, TagPeeker};
3use picky_asn1::wrapper::{BmpStringAsn1, PrintableStringAsn1};
4use serde::{de, ser};
5use std::borrow::Cow;
6use std::fmt;
7
8/// [RFC 5280 #4.1.2.4](https://tools.ietf.org/html/rfc5280#section-4.1.2.4)
9///
10/// TeletexString, UniversalString and BmpString are not supported.
11///
12/// ```not_rust
13/// DirectoryString ::= CHOICE {
14///      teletexString       TeletexString   (SIZE (1..MAX)),
15///      printableString     PrintableString (SIZE (1..MAX)),
16///      universalString     UniversalString (SIZE (1..MAX)),
17///      utf8String          UTF8String      (SIZE (1..MAX)),
18///      bmpString           BMPString       (SIZE (1..MAX)) }
19/// ```
20#[derive(Debug, PartialEq, Eq, Clone)]
21pub enum DirectoryString {
22    //TeletexString,
23    PrintableString(PrintableStringAsn1),
24    //UniversalString,
25    Utf8String(String),
26    BmpString(BmpStringAsn1),
27}
28
29impl fmt::Display for DirectoryString {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        write!(f, "{}", self.to_utf8_lossy())
32    }
33}
34
35impl DirectoryString {
36    pub fn to_utf8_lossy(&self) -> Cow<str> {
37        match &self {
38            DirectoryString::PrintableString(string) => String::from_utf8_lossy(string.as_bytes()),
39            DirectoryString::Utf8String(string) => Cow::Borrowed(string.as_str()),
40            DirectoryString::BmpString(string) => Cow::Owned(utf16_to_utf8_lossy(string.as_bytes())),
41        }
42    }
43
44    pub fn as_bytes(&self) -> &[u8] {
45        match &self {
46            DirectoryString::PrintableString(string) => string.as_bytes(),
47            DirectoryString::Utf8String(string) => string.as_bytes(),
48            DirectoryString::BmpString(string) => string.as_bytes(),
49        }
50    }
51}
52
53impl From<&str> for DirectoryString {
54    fn from(string: &str) -> Self {
55        Self::Utf8String(string.to_owned())
56    }
57}
58
59impl From<String> for DirectoryString {
60    fn from(string: String) -> Self {
61        Self::Utf8String(string)
62    }
63}
64
65impl From<PrintableString> for DirectoryString {
66    fn from(string: PrintableString) -> Self {
67        Self::PrintableString(string.into())
68    }
69}
70
71impl From<Utf8String> for DirectoryString {
72    fn from(string: Utf8String) -> Self {
73        Self::Utf8String(String::from_utf8(string.into_bytes()).expect("Utf8String has the right charset"))
74    }
75}
76
77impl From<PrintableStringAsn1> for DirectoryString {
78    fn from(string: PrintableStringAsn1) -> Self {
79        Self::PrintableString(string)
80    }
81}
82
83impl From<BmpStringAsn1> for DirectoryString {
84    fn from(string: BmpStringAsn1) -> Self {
85        Self::BmpString(string)
86    }
87}
88
89impl From<DirectoryString> for String {
90    fn from(ds: DirectoryString) -> Self {
91        match ds {
92            DirectoryString::PrintableString(string) => String::from_utf8_lossy(string.as_bytes()).into(),
93            DirectoryString::Utf8String(string) => string,
94            DirectoryString::BmpString(string) => utf16_to_utf8_lossy(string.as_bytes()),
95        }
96    }
97}
98
99impl ser::Serialize for DirectoryString {
100    fn serialize<S>(&self, serializer: S) -> Result<<S as ser::Serializer>::Ok, <S as ser::Serializer>::Error>
101    where
102        S: ser::Serializer,
103    {
104        match &self {
105            DirectoryString::PrintableString(string) => string.serialize(serializer),
106            DirectoryString::Utf8String(string) => string.serialize(serializer),
107            DirectoryString::BmpString(string) => string.serialize(serializer),
108        }
109    }
110}
111
112impl<'de> de::Deserialize<'de> for DirectoryString {
113    fn deserialize<D>(deserializer: D) -> Result<Self, <D as de::Deserializer<'de>>::Error>
114    where
115        D: de::Deserializer<'de>,
116    {
117        struct Visitor;
118
119        impl<'de> de::Visitor<'de> for Visitor {
120            type Value = DirectoryString;
121
122            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
123                formatter.write_str("a valid DER-encoded DirectoryString")
124            }
125
126            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
127            where
128                A: de::SeqAccess<'de>,
129            {
130                let tag_peeker: TagPeeker = seq_next_element!(seq, DirectoryString, "choice tag");
131                match tag_peeker.next_tag {
132                    Tag::UTF8_STRING => Ok(DirectoryString::Utf8String(seq_next_element!(
133                        seq,
134                        DirectoryString,
135                        "Utf8String"
136                    ))),
137                    Tag::PRINTABLE_STRING => Ok(DirectoryString::PrintableString(seq_next_element!(
138                        seq,
139                        DirectoryString,
140                        "PrintableString"
141                    ))),
142                    Tag::BMP_STRING => Ok(DirectoryString::BmpString(seq_next_element!(
143                        seq,
144                        DirectoryString,
145                        "BmpString"
146                    ))),
147                    Tag::TELETEX_STRING => Err(serde_invalid_value!(
148                        DirectoryString,
149                        "TeletexString not supported",
150                        "a supported string type"
151                    )),
152                    Tag::VIDEOTEX_STRING => Err(serde_invalid_value!(
153                        DirectoryString,
154                        "VideotexString not supported",
155                        "a supported string type"
156                    )),
157                    Tag::IA5_STRING => Err(serde_invalid_value!(
158                        DirectoryString,
159                        "IA5String not supported",
160                        "a supported string type"
161                    )),
162                    _ => Err(serde_invalid_value!(
163                        DirectoryString,
164                        "unknown string type",
165                        "a known supported string type"
166                    )),
167                }
168            }
169        }
170
171        deserializer.deserialize_enum("DirectoryString", &["PrintableString", "Utf8String"], Visitor)
172    }
173}
174
175fn utf16_to_utf8_lossy(data: &[u8]) -> String {
176    debug_assert_eq!(data.len() % 2, 0);
177    String::from_utf16_lossy(
178        &data
179            .chunks(2)
180            .map(|c| u16::from_be_bytes(c.try_into().unwrap()))
181            .collect::<Vec<u16>>(),
182    )
183}