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