use std::{fmt, str::FromStr};
use crate::{Error, InvalidAsn1String};
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct PrintableString(String);
impl PrintableString {
pub fn as_str(&self) -> &str {
&self.0
}
}
impl TryFrom<&str> for PrintableString {
type Error = Error;
fn try_from(input: &str) -> Result<Self, Error> {
input.to_string().try_into()
}
}
impl TryFrom<String> for PrintableString {
type Error = Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
for &c in value.as_bytes() {
match c {
b'A'..=b'Z'
| b'a'..=b'z'
| b'0'..=b'9'
| b' '
| b'\''
| b'('
| b')'
| b'+'
| b','
| b'-'
| b'.'
| b'/'
| b':'
| b'='
| b'?' => (),
_ => {
return Err(Error::InvalidAsn1String(
InvalidAsn1String::PrintableString(value),
))
},
}
}
Ok(Self(value))
}
}
impl FromStr for PrintableString {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.try_into()
}
}
impl AsRef<str> for PrintableString {
fn as_ref(&self) -> &str {
&self.0
}
}
impl fmt::Display for PrintableString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.as_str(), f)
}
}
impl PartialEq<str> for PrintableString {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<String> for PrintableString {
fn eq(&self, other: &String) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq<&str> for PrintableString {
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
impl PartialEq<&String> for PrintableString {
fn eq(&self, other: &&String) -> bool {
self.as_str() == other.as_str()
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Ia5String(String);
impl Ia5String {
pub fn as_str(&self) -> &str {
&self.0
}
}
impl TryFrom<&str> for Ia5String {
type Error = Error;
fn try_from(input: &str) -> Result<Self, Error> {
input.to_string().try_into()
}
}
impl TryFrom<String> for Ia5String {
type Error = Error;
fn try_from(input: String) -> Result<Self, Error> {
if !input.is_ascii() {
return Err(Error::InvalidAsn1String(InvalidAsn1String::Ia5String(
input,
)));
}
Ok(Self(input))
}
}
impl FromStr for Ia5String {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.try_into()
}
}
impl AsRef<str> for Ia5String {
fn as_ref(&self) -> &str {
&self.0
}
}
impl fmt::Display for Ia5String {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.as_str(), f)
}
}
impl PartialEq<str> for Ia5String {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<String> for Ia5String {
fn eq(&self, other: &String) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq<&str> for Ia5String {
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
impl PartialEq<&String> for Ia5String {
fn eq(&self, other: &&String) -> bool {
self.as_str() == other.as_str()
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct TeletexString(String);
impl TeletexString {
pub fn as_str(&self) -> &str {
&self.0
}
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
}
impl TryFrom<&str> for TeletexString {
type Error = Error;
fn try_from(input: &str) -> Result<Self, Error> {
input.to_string().try_into()
}
}
impl TryFrom<String> for TeletexString {
type Error = Error;
fn try_from(input: String) -> Result<Self, Error> {
if !input.as_bytes().iter().all(|b| (0x20..=0x7f).contains(b)) {
return Err(Error::InvalidAsn1String(InvalidAsn1String::TeletexString(
input,
)));
}
Ok(Self(input))
}
}
impl FromStr for TeletexString {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.try_into()
}
}
impl AsRef<str> for TeletexString {
fn as_ref(&self) -> &str {
&self.0
}
}
impl fmt::Display for TeletexString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.as_str(), f)
}
}
impl PartialEq<str> for TeletexString {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<String> for TeletexString {
fn eq(&self, other: &String) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq<&str> for TeletexString {
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
impl PartialEq<&String> for TeletexString {
fn eq(&self, other: &&String) -> bool {
self.as_str() == other.as_str()
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct BmpString(Vec<u8>);
impl BmpString {
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn from_utf16be(vec: Vec<u8>) -> Result<Self, Error> {
if vec.len() % 2 != 0 {
return Err(Error::InvalidAsn1String(InvalidAsn1String::BmpString(
"Invalid UTF-16 encoding".to_string(),
)));
}
for maybe_char in char::decode_utf16(
vec.chunks_exact(2)
.map(|chunk| u16::from_be_bytes([chunk[0], chunk[1]])),
) {
match maybe_char {
Ok(c) if (c as u64) < u64::from(u16::MAX) => (),
_ => {
return Err(Error::InvalidAsn1String(InvalidAsn1String::BmpString(
"Invalid UTF-16 encoding".to_string(),
)));
},
}
}
Ok(Self(vec.to_vec()))
}
}
impl TryFrom<&str> for BmpString {
type Error = Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
let capacity = value.len().checked_mul(2).ok_or_else(|| {
Error::InvalidAsn1String(InvalidAsn1String::BmpString(value.to_string()))
})?;
let mut bytes = Vec::with_capacity(capacity);
for code_point in value.encode_utf16() {
bytes.extend(code_point.to_be_bytes());
}
BmpString::from_utf16be(bytes)
}
}
impl TryFrom<String> for BmpString {
type Error = Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
value.as_str().try_into()
}
}
impl FromStr for BmpString {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.try_into()
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct UniversalString(Vec<u8>);
impl UniversalString {
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn from_utf32be(vec: Vec<u8>) -> Result<UniversalString, Error> {
if vec.len() % 4 != 0 {
return Err(Error::InvalidAsn1String(
InvalidAsn1String::UniversalString("Invalid UTF-32 encoding".to_string()),
));
}
for maybe_char in vec
.chunks_exact(4)
.map(|chunk| u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
{
if core::char::from_u32(maybe_char).is_none() {
return Err(Error::InvalidAsn1String(
InvalidAsn1String::UniversalString("Invalid UTF-32 encoding".to_string()),
));
}
}
Ok(Self(vec))
}
}
impl TryFrom<&str> for UniversalString {
type Error = Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
let capacity = value.len().checked_mul(4).ok_or_else(|| {
Error::InvalidAsn1String(InvalidAsn1String::UniversalString(value.to_string()))
})?;
let mut bytes = Vec::with_capacity(capacity);
for char in value.chars().map(|char| char as u32) {
bytes.extend(char.to_be_bytes())
}
UniversalString::from_utf32be(bytes)
}
}
impl TryFrom<String> for UniversalString {
type Error = Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
value.as_str().try_into()
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use crate::{BmpString, Ia5String, PrintableString, TeletexString, UniversalString};
#[test]
fn printable_string() {
const EXAMPLE_UTF8: &str = "CertificateTemplate";
let printable_string = PrintableString::try_from(EXAMPLE_UTF8).unwrap();
assert_eq!(printable_string, EXAMPLE_UTF8);
assert!(PrintableString::try_from("@").is_err());
assert!(PrintableString::try_from("*").is_err());
}
#[test]
fn ia5_string() {
const EXAMPLE_UTF8: &str = "CertificateTemplate";
let ia5_string = Ia5String::try_from(EXAMPLE_UTF8).unwrap();
assert_eq!(ia5_string, EXAMPLE_UTF8);
assert!(Ia5String::try_from(String::from('\u{7F}')).is_ok());
assert!(Ia5String::try_from(String::from('\u{8F}')).is_err());
}
#[test]
fn teletext_string() {
const EXAMPLE_UTF8: &str = "CertificateTemplate";
let teletext_string = TeletexString::try_from(EXAMPLE_UTF8).unwrap();
assert_eq!(teletext_string, EXAMPLE_UTF8);
assert!(Ia5String::try_from(String::from('\u{7F}')).is_ok());
assert!(Ia5String::try_from(String::from('\u{8F}')).is_err());
}
#[test]
fn bmp_string() {
const EXPECTED_BYTES: &[u8] = &[
0x00, 0x43, 0x00, 0x65, 0x00, 0x72, 0x00, 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69,
0x00, 0x63, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x54, 0x00, 0x65, 0x00, 0x6d,
0x00, 0x70, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65,
];
const EXAMPLE_UTF8: &str = "CertificateTemplate";
let bmp_string = BmpString::try_from(EXAMPLE_UTF8).unwrap();
assert_eq!(bmp_string.as_bytes(), EXPECTED_BYTES);
assert!(BmpString::try_from(String::from('\u{FFFE}')).is_ok());
assert!(BmpString::try_from(String::from('\u{FFFF}')).is_err());
}
#[test]
fn universal_string() {
const EXPECTED_BYTES: &[u8] = &[
0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00,
0x00, 0x74, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x69,
0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00,
0x00, 0x65, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x6d,
0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00,
0x00, 0x74, 0x00, 0x00, 0x00, 0x65,
];
const EXAMPLE_UTF8: &str = "CertificateTemplate";
let universal_string = UniversalString::try_from(EXAMPLE_UTF8).unwrap();
assert_eq!(universal_string.as_bytes(), EXPECTED_BYTES);
}
}