1use std::fmt;
4use std::str::FromStr;
5
6use crate::{Error, InvalidAsn1String};
7
8#[derive(Debug, PartialEq, Eq, Hash, Clone)]
50pub struct PrintableString(String);
51
52impl PrintableString {
53 pub fn as_str(&self) -> &str {
55 &self.0
56 }
57}
58
59impl TryFrom<&str> for PrintableString {
60 type Error = Error;
61
62 fn try_from(input: &str) -> Result<Self, Error> {
69 input.to_string().try_into()
70 }
71}
72
73impl TryFrom<String> for PrintableString {
74 type Error = Error;
75
76 fn try_from(value: String) -> Result<Self, Self::Error> {
83 for &c in value.as_bytes() {
84 match c {
85 b'A'..=b'Z'
86 | b'a'..=b'z'
87 | b'0'..=b'9'
88 | b' '
89 | b'\''
90 | b'('
91 | b')'
92 | b'+'
93 | b','
94 | b'-'
95 | b'.'
96 | b'/'
97 | b':'
98 | b'='
99 | b'?' => (),
100 _ => {
101 return Err(Error::InvalidAsn1String(
102 InvalidAsn1String::PrintableString(value),
103 ))
104 },
105 }
106 }
107 Ok(Self(value))
108 }
109}
110
111impl FromStr for PrintableString {
112 type Err = Error;
113
114 fn from_str(s: &str) -> Result<Self, Self::Err> {
115 s.try_into()
116 }
117}
118
119impl AsRef<str> for PrintableString {
120 fn as_ref(&self) -> &str {
121 &self.0
122 }
123}
124
125impl fmt::Display for PrintableString {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 fmt::Display::fmt(self.as_str(), f)
128 }
129}
130
131impl PartialEq<str> for PrintableString {
132 fn eq(&self, other: &str) -> bool {
133 self.as_str() == other
134 }
135}
136
137impl PartialEq<String> for PrintableString {
138 fn eq(&self, other: &String) -> bool {
139 self.as_str() == other.as_str()
140 }
141}
142
143impl PartialEq<&str> for PrintableString {
144 fn eq(&self, other: &&str) -> bool {
145 self.as_str() == *other
146 }
147}
148
149impl PartialEq<&String> for PrintableString {
150 fn eq(&self, other: &&String) -> bool {
151 self.as_str() == other.as_str()
152 }
153}
154
155#[derive(Debug, PartialEq, Eq, Hash, Clone)]
177pub struct Ia5String(String);
178
179impl Ia5String {
180 pub fn as_str(&self) -> &str {
182 &self.0
183 }
184}
185
186impl TryFrom<&str> for Ia5String {
187 type Error = Error;
188
189 fn try_from(input: &str) -> Result<Self, Error> {
196 input.to_string().try_into()
197 }
198}
199
200impl TryFrom<String> for Ia5String {
201 type Error = Error;
202
203 fn try_from(input: String) -> Result<Self, Error> {
208 if !input.is_ascii() {
209 return Err(Error::InvalidAsn1String(InvalidAsn1String::Ia5String(
210 input,
211 )));
212 }
213 Ok(Self(input))
214 }
215}
216
217impl FromStr for Ia5String {
218 type Err = Error;
219
220 fn from_str(s: &str) -> Result<Self, Self::Err> {
221 s.try_into()
222 }
223}
224
225impl AsRef<str> for Ia5String {
226 fn as_ref(&self) -> &str {
227 &self.0
228 }
229}
230
231impl fmt::Display for Ia5String {
232 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233 fmt::Display::fmt(self.as_str(), f)
234 }
235}
236
237impl PartialEq<str> for Ia5String {
238 fn eq(&self, other: &str) -> bool {
239 self.as_str() == other
240 }
241}
242
243impl PartialEq<String> for Ia5String {
244 fn eq(&self, other: &String) -> bool {
245 self.as_str() == other.as_str()
246 }
247}
248
249impl PartialEq<&str> for Ia5String {
250 fn eq(&self, other: &&str) -> bool {
251 self.as_str() == *other
252 }
253}
254
255impl PartialEq<&String> for Ia5String {
256 fn eq(&self, other: &&String) -> bool {
257 self.as_str() == other.as_str()
258 }
259}
260
261#[derive(Debug, PartialEq, Eq, Hash, Clone)]
284pub struct TeletexString(String);
285
286impl TeletexString {
287 pub fn as_str(&self) -> &str {
289 &self.0
290 }
291
292 pub fn as_bytes(&self) -> &[u8] {
294 self.0.as_bytes()
295 }
296}
297
298impl TryFrom<&str> for TeletexString {
299 type Error = Error;
300
301 fn try_from(input: &str) -> Result<Self, Error> {
308 input.to_string().try_into()
309 }
310}
311
312impl TryFrom<String> for TeletexString {
313 type Error = Error;
314
315 fn try_from(input: String) -> Result<Self, Error> {
322 if !input.as_bytes().iter().all(|b| (0x20..=0x7f).contains(b)) {
324 return Err(Error::InvalidAsn1String(InvalidAsn1String::TeletexString(
325 input,
326 )));
327 }
328 Ok(Self(input))
329 }
330}
331
332impl FromStr for TeletexString {
333 type Err = Error;
334
335 fn from_str(s: &str) -> Result<Self, Self::Err> {
336 s.try_into()
337 }
338}
339
340impl AsRef<str> for TeletexString {
341 fn as_ref(&self) -> &str {
342 &self.0
343 }
344}
345
346impl fmt::Display for TeletexString {
347 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348 fmt::Display::fmt(self.as_str(), f)
349 }
350}
351
352impl PartialEq<str> for TeletexString {
353 fn eq(&self, other: &str) -> bool {
354 self.as_str() == other
355 }
356}
357
358impl PartialEq<String> for TeletexString {
359 fn eq(&self, other: &String) -> bool {
360 self.as_str() == other.as_str()
361 }
362}
363
364impl PartialEq<&str> for TeletexString {
365 fn eq(&self, other: &&str) -> bool {
366 self.as_str() == *other
367 }
368}
369
370impl PartialEq<&String> for TeletexString {
371 fn eq(&self, other: &&String) -> bool {
372 self.as_str() == other.as_str()
373 }
374}
375
376#[derive(Debug, PartialEq, Eq, Hash, Clone)]
399pub struct BmpString(Vec<u8>);
400
401impl BmpString {
402 pub fn as_bytes(&self) -> &[u8] {
417 &self.0
418 }
419
420 pub fn from_utf16be(vec: Vec<u8>) -> Result<Self, Error> {
422 if vec.len() % 2 != 0 {
423 return Err(Error::InvalidAsn1String(InvalidAsn1String::BmpString(
424 "Invalid UTF-16 encoding".to_string(),
425 )));
426 }
427
428 for maybe_char in char::decode_utf16(
430 vec.chunks_exact(2)
431 .map(|chunk| u16::from_be_bytes([chunk[0], chunk[1]])),
432 ) {
433 match maybe_char {
435 Ok(c) if (c as u64) < u64::from(u16::MAX) => (),
437 _ => {
439 return Err(Error::InvalidAsn1String(InvalidAsn1String::BmpString(
440 "Invalid UTF-16 encoding".to_string(),
441 )));
442 },
443 }
444 }
445 Ok(Self(vec.to_vec()))
446 }
447}
448
449impl TryFrom<&str> for BmpString {
450 type Error = Error;
451
452 fn try_from(value: &str) -> Result<Self, Self::Error> {
459 let capacity = value.len().checked_mul(2).ok_or_else(|| {
460 Error::InvalidAsn1String(InvalidAsn1String::BmpString(value.to_string()))
461 })?;
462
463 let mut bytes = Vec::with_capacity(capacity);
464
465 for code_point in value.encode_utf16() {
466 bytes.extend(code_point.to_be_bytes());
467 }
468
469 BmpString::from_utf16be(bytes)
470 }
471}
472
473impl TryFrom<String> for BmpString {
474 type Error = Error;
475
476 fn try_from(value: String) -> Result<Self, Self::Error> {
483 value.as_str().try_into()
484 }
485}
486
487impl FromStr for BmpString {
488 type Err = Error;
489
490 fn from_str(s: &str) -> Result<Self, Self::Err> {
491 s.try_into()
492 }
493}
494
495#[derive(Debug, PartialEq, Eq, Hash, Clone)]
518pub struct UniversalString(Vec<u8>);
519
520impl UniversalString {
521 pub fn as_bytes(&self) -> &[u8] {
536 &self.0
537 }
538
539 pub fn from_utf32be(vec: Vec<u8>) -> Result<UniversalString, Error> {
541 if vec.len() % 4 != 0 {
542 return Err(Error::InvalidAsn1String(
543 InvalidAsn1String::UniversalString("Invalid UTF-32 encoding".to_string()),
544 ));
545 }
546
547 for maybe_char in vec
549 .chunks_exact(4)
550 .map(|chunk| u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
551 {
552 if core::char::from_u32(maybe_char).is_none() {
553 return Err(Error::InvalidAsn1String(
554 InvalidAsn1String::UniversalString("Invalid UTF-32 encoding".to_string()),
555 ));
556 }
557 }
558
559 Ok(Self(vec))
560 }
561}
562
563impl TryFrom<&str> for UniversalString {
564 type Error = Error;
565
566 fn try_from(value: &str) -> Result<Self, Self::Error> {
573 let capacity = value.len().checked_mul(4).ok_or_else(|| {
574 Error::InvalidAsn1String(InvalidAsn1String::UniversalString(value.to_string()))
575 })?;
576
577 let mut bytes = Vec::with_capacity(capacity);
578
579 for char in value.chars().map(|char| char as u32) {
584 bytes.extend(char.to_be_bytes())
585 }
586
587 UniversalString::from_utf32be(bytes)
588 }
589}
590
591impl TryFrom<String> for UniversalString {
592 type Error = Error;
593
594 fn try_from(value: String) -> Result<Self, Self::Error> {
601 value.as_str().try_into()
602 }
603}
604
605#[cfg(test)]
606#[allow(clippy::unwrap_used)]
607mod tests {
608
609 use crate::{BmpString, Ia5String, PrintableString, TeletexString, UniversalString};
610
611 #[test]
612 fn printable_string() {
613 const EXAMPLE_UTF8: &str = "CertificateTemplate";
614 let printable_string = PrintableString::try_from(EXAMPLE_UTF8).unwrap();
615 assert_eq!(printable_string, EXAMPLE_UTF8);
616 assert!(PrintableString::try_from("@").is_err());
617 assert!(PrintableString::try_from("*").is_err());
618 }
619
620 #[test]
621 fn ia5_string() {
622 const EXAMPLE_UTF8: &str = "CertificateTemplate";
623 let ia5_string = Ia5String::try_from(EXAMPLE_UTF8).unwrap();
624 assert_eq!(ia5_string, EXAMPLE_UTF8);
625 assert!(Ia5String::try_from(String::from('\u{7F}')).is_ok());
626 assert!(Ia5String::try_from(String::from('\u{8F}')).is_err());
627 }
628
629 #[test]
630 fn teletext_string() {
631 const EXAMPLE_UTF8: &str = "CertificateTemplate";
632 let teletext_string = TeletexString::try_from(EXAMPLE_UTF8).unwrap();
633 assert_eq!(teletext_string, EXAMPLE_UTF8);
634 assert!(Ia5String::try_from(String::from('\u{7F}')).is_ok());
635 assert!(Ia5String::try_from(String::from('\u{8F}')).is_err());
636 }
637
638 #[test]
639 fn bmp_string() {
640 const EXPECTED_BYTES: &[u8] = &[
641 0x00, 0x43, 0x00, 0x65, 0x00, 0x72, 0x00, 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69,
642 0x00, 0x63, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x54, 0x00, 0x65, 0x00, 0x6d,
643 0x00, 0x70, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65,
644 ];
645 const EXAMPLE_UTF8: &str = "CertificateTemplate";
646 let bmp_string = BmpString::try_from(EXAMPLE_UTF8).unwrap();
647 assert_eq!(bmp_string.as_bytes(), EXPECTED_BYTES);
648 assert!(BmpString::try_from(String::from('\u{FFFE}')).is_ok());
649 assert!(BmpString::try_from(String::from('\u{FFFF}')).is_err());
650 }
651
652 #[test]
653 fn universal_string() {
654 const EXPECTED_BYTES: &[u8] = &[
655 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00,
656 0x00, 0x74, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x69,
657 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00,
658 0x00, 0x65, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x6d,
659 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00,
660 0x00, 0x74, 0x00, 0x00, 0x00, 0x65,
661 ];
662 const EXAMPLE_UTF8: &str = "CertificateTemplate";
663 let universal_string = UniversalString::try_from(EXAMPLE_UTF8).unwrap();
664 assert_eq!(universal_string.as_bytes(), EXPECTED_BYTES);
665 }
666}