diff options
Diffstat (limited to 'src/asn1_types/strings')
-rw-r--r-- | src/asn1_types/strings/bmpstring.rs | 132 | ||||
-rw-r--r-- | src/asn1_types/strings/generalstring.rs | 14 | ||||
-rw-r--r-- | src/asn1_types/strings/graphicstring.rs | 14 | ||||
-rw-r--r-- | src/asn1_types/strings/ia5string.rs | 14 | ||||
-rw-r--r-- | src/asn1_types/strings/numericstring.rs | 18 | ||||
-rw-r--r-- | src/asn1_types/strings/printablestring.rs | 35 | ||||
-rw-r--r-- | src/asn1_types/strings/str.rs | 67 | ||||
-rw-r--r-- | src/asn1_types/strings/string.rs | 64 | ||||
-rw-r--r-- | src/asn1_types/strings/teletexstring.rs | 18 | ||||
-rw-r--r-- | src/asn1_types/strings/universalstring.rs | 137 | ||||
-rw-r--r-- | src/asn1_types/strings/utf8string.rs | 13 | ||||
-rw-r--r-- | src/asn1_types/strings/videotexstring.rs | 19 | ||||
-rw-r--r-- | src/asn1_types/strings/visiblestring.rs | 18 |
13 files changed, 563 insertions, 0 deletions
diff --git a/src/asn1_types/strings/bmpstring.rs b/src/asn1_types/strings/bmpstring.rs new file mode 100644 index 0000000..490c68c --- /dev/null +++ b/src/asn1_types/strings/bmpstring.rs @@ -0,0 +1,132 @@ +// do not use the `asn1_string` macro, since types are not the same +// X.680 section 37.15 + +use crate::*; +use alloc::borrow::Cow; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; + +/// ASN.1 `BMPSTRING` type +/// +/// Note: parsing a `BmpString` allocates memory since the UTF-16 to UTF-8 conversion requires a memory allocation. +/// (see `String::from_utf16` method). +#[derive(Debug, PartialEq, Eq)] +pub struct BmpString<'a> { + pub(crate) data: Cow<'a, str>, +} + +impl<'a> BmpString<'a> { + pub const fn new(s: &'a str) -> Self { + BmpString { + data: Cow::Borrowed(s), + } + } + + pub fn string(&self) -> String { + self.data.to_string() + } +} + +impl<'a> AsRef<str> for BmpString<'a> { + fn as_ref(&self) -> &str { + &self.data + } +} + +impl<'a> From<&'a str> for BmpString<'a> { + fn from(s: &'a str) -> Self { + Self::new(s) + } +} + +impl From<String> for BmpString<'_> { + fn from(s: String) -> Self { + Self { + data: alloc::borrow::Cow::Owned(s), + } + } +} + +impl<'a> core::convert::TryFrom<Any<'a>> for BmpString<'a> { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<BmpString<'a>> { + any.tag().assert_eq(Self::TAG)?; + + // read slice as big-endian UTF-16 string + let v = &any + .data + .chunks(2) + .map(|s| match s { + [a, b] => ((*a as u16) << 8) | (*b as u16), + [a] => *a as u16, + _ => unreachable!(), + }) + .collect::<Vec<_>>(); + + let s = String::from_utf16(v)?; + let data = Cow::Owned(s); + + Ok(BmpString { data }) + } +} + +impl<'a> CheckDerConstraints for BmpString<'a> { + fn check_constraints(any: &Any) -> Result<()> { + any.header.assert_primitive()?; + Ok(()) + } +} + +impl DerAutoDerive for BmpString<'_> {} + +impl<'a> Tagged for BmpString<'a> { + const TAG: Tag = Tag::BmpString; +} + +impl<'a> TestValidCharset for BmpString<'a> { + fn test_valid_charset(i: &[u8]) -> Result<()> { + if i.len() % 2 != 0 { + return Err(Error::StringInvalidCharset); + } + let iter = i.chunks(2).map(|s| ((s[0] as u16) << 8) | (s[1] as u16)); + for c in char::decode_utf16(iter) { + if c.is_err() { + return Err(Error::StringInvalidCharset); + } + } + Ok(()) + } +} + +#[cfg(feature = "std")] +impl ToDer for BmpString<'_> { + fn to_der_len(&self) -> Result<usize> { + // compute the UTF-16 length + let sz = self.data.encode_utf16().count() * 2; + if sz < 127 { + // 1 (class+tag) + 1 (length) + len + Ok(2 + sz) + } else { + // 1 (class+tag) + n (length) + len + let n = Length::Definite(sz).to_der_len()?; + Ok(1 + n + sz) + } + } + + fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + // compute the UTF-16 length + let l = self.data.encode_utf16().count() * 2; + let header = Header::new(Class::Universal, false, Self::TAG, Length::Definite(l)); + header.write_der_header(writer).map_err(Into::into) + } + + fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let mut v = Vec::new(); + for u in self.data.encode_utf16() { + v.push((u >> 8) as u8); + v.push((u & 0xff) as u8); + } + writer.write(&v).map_err(Into::into) + } +} diff --git a/src/asn1_types/strings/generalstring.rs b/src/asn1_types/strings/generalstring.rs new file mode 100644 index 0000000..f455d6a --- /dev/null +++ b/src/asn1_types/strings/generalstring.rs @@ -0,0 +1,14 @@ +use crate::{asn1_string, TestValidCharset}; +use crate::{Error, Result}; +use alloc::string::String; + +asn1_string!(GeneralString); + +impl<'a> TestValidCharset for GeneralString<'a> { + fn test_valid_charset(i: &[u8]) -> Result<()> { + if !i.iter().all(u8::is_ascii) { + return Err(Error::StringInvalidCharset); + } + Ok(()) + } +} diff --git a/src/asn1_types/strings/graphicstring.rs b/src/asn1_types/strings/graphicstring.rs new file mode 100644 index 0000000..3d84040 --- /dev/null +++ b/src/asn1_types/strings/graphicstring.rs @@ -0,0 +1,14 @@ +use crate::{asn1_string, TestValidCharset}; +use crate::{Error, Result}; +use alloc::string::String; + +asn1_string!(GraphicString); + +impl<'a> TestValidCharset for GraphicString<'a> { + fn test_valid_charset(i: &[u8]) -> Result<()> { + if !i.iter().all(u8::is_ascii) { + return Err(Error::StringInvalidCharset); + } + Ok(()) + } +} diff --git a/src/asn1_types/strings/ia5string.rs b/src/asn1_types/strings/ia5string.rs new file mode 100644 index 0000000..4b37465 --- /dev/null +++ b/src/asn1_types/strings/ia5string.rs @@ -0,0 +1,14 @@ +use crate::{asn1_string, TestValidCharset}; +use crate::{Error, Result}; +use alloc::string::String; + +asn1_string!(Ia5String); + +impl<'a> TestValidCharset for Ia5String<'a> { + fn test_valid_charset(i: &[u8]) -> Result<()> { + if !i.iter().all(u8::is_ascii) { + return Err(Error::StringInvalidCharset); + } + Ok(()) + } +} diff --git a/src/asn1_types/strings/numericstring.rs b/src/asn1_types/strings/numericstring.rs new file mode 100644 index 0000000..dbe4ff1 --- /dev/null +++ b/src/asn1_types/strings/numericstring.rs @@ -0,0 +1,18 @@ +use crate::{asn1_string, TestValidCharset}; +use crate::{Error, Result}; +use alloc::string::String; + +asn1_string!(NumericString); + +impl<'a> TestValidCharset for NumericString<'a> { + fn test_valid_charset(i: &[u8]) -> Result<()> { + #[allow(clippy::trivially_copy_pass_by_ref)] + fn is_numeric(b: &u8) -> bool { + matches!(*b, b'0'..=b'9' | b' ') + } + if !i.iter().all(is_numeric) { + return Err(Error::StringInvalidCharset); + } + Ok(()) + } +} diff --git a/src/asn1_types/strings/printablestring.rs b/src/asn1_types/strings/printablestring.rs new file mode 100644 index 0000000..5cd51fa --- /dev/null +++ b/src/asn1_types/strings/printablestring.rs @@ -0,0 +1,35 @@ +use crate::{asn1_string, TestValidCharset}; +use crate::{Error, Result}; +use alloc::string::String; + +asn1_string!(PrintableString); + +impl<'a> TestValidCharset for PrintableString<'a> { + fn test_valid_charset(i: &[u8]) -> Result<()> { + // Argument must be a reference, because of the .iter().all(F) call below + #[allow(clippy::trivially_copy_pass_by_ref)] + fn is_printable(b: &u8) -> bool { + matches!(*b, + b'a'..=b'z' + | b'A'..=b'Z' + | b'0'..=b'9' + | b' ' + | b'\'' + | b'(' + | b')' + | b'+' + | b',' + | b'-' + | b'.' + | b'/' + | b':' + | b'=' + | b'?') + } + + if !i.iter().all(is_printable) { + return Err(Error::StringInvalidCharset); + } + Ok(()) + } +} diff --git a/src/asn1_types/strings/str.rs b/src/asn1_types/strings/str.rs new file mode 100644 index 0000000..a90e8e3 --- /dev/null +++ b/src/asn1_types/strings/str.rs @@ -0,0 +1,67 @@ +use crate::*; +use alloc::borrow::Cow; +use core::convert::TryFrom; + +impl<'a> TryFrom<Any<'a>> for &'a str { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<&'a str> { + TryFrom::try_from(&any) + } +} + +impl<'a, 'b> TryFrom<&'b Any<'a>> for &'a str { + type Error = Error; + + fn try_from(any: &'b Any<'a>) -> Result<&'a str> { + any.tag().assert_eq(Self::TAG)?; + let s = Utf8String::try_from(any)?; + match s.data { + Cow::Borrowed(s) => Ok(s), + Cow::Owned(_) => Err(Error::LifetimeError), + } + } +} + +impl<'a> CheckDerConstraints for &'a str { + fn check_constraints(any: &Any) -> Result<()> { + // X.690 section 10.2 + any.header.assert_primitive()?; + Ok(()) + } +} + +impl DerAutoDerive for &'_ str {} + +impl<'a> Tagged for &'a str { + const TAG: Tag = Tag::Utf8String; +} + +#[cfg(feature = "std")] +impl<'a> ToDer for &'a str { + fn to_der_len(&self) -> Result<usize> { + let sz = self.as_bytes().len(); + if sz < 127 { + // 1 (class+tag) + 1 (length) + len + Ok(2 + sz) + } else { + // 1 (class+tag) + n (length) + len + let n = Length::Definite(sz).to_der_len()?; + Ok(1 + n + sz) + } + } + + fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let header = Header::new( + Class::Universal, + false, + Self::TAG, + Length::Definite(self.as_bytes().len()), + ); + header.write_der_header(writer).map_err(Into::into) + } + + fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + writer.write(self.as_bytes()).map_err(Into::into) + } +} diff --git a/src/asn1_types/strings/string.rs b/src/asn1_types/strings/string.rs new file mode 100644 index 0000000..81f6ba8 --- /dev/null +++ b/src/asn1_types/strings/string.rs @@ -0,0 +1,64 @@ +use crate::*; +use alloc::string::String; +use core::convert::TryFrom; + +impl<'a> TryFrom<Any<'a>> for String { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<String> { + TryFrom::try_from(&any) + } +} + +impl<'a, 'b> TryFrom<&'b Any<'a>> for String { + type Error = Error; + + fn try_from(any: &'b Any<'a>) -> Result<String> { + any.tag().assert_eq(Self::TAG)?; + let s = Utf8String::try_from(any)?; + Ok(s.data.into_owned()) + } +} + +impl CheckDerConstraints for String { + fn check_constraints(any: &Any) -> Result<()> { + // X.690 section 10.2 + any.header.assert_primitive()?; + Ok(()) + } +} + +impl DerAutoDerive for String {} + +impl Tagged for String { + const TAG: Tag = Tag::Utf8String; +} + +#[cfg(feature = "std")] +impl ToDer for String { + fn to_der_len(&self) -> Result<usize> { + let sz = self.as_bytes().len(); + if sz < 127 { + // 1 (class+tag) + 1 (length) + len + Ok(2 + sz) + } else { + // 1 (class+tag) + n (length) + len + let n = Length::Definite(sz).to_der_len()?; + Ok(1 + n + sz) + } + } + + fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let header = Header::new( + Class::Universal, + false, + Self::TAG, + Length::Definite(self.len()), + ); + header.write_der_header(writer).map_err(Into::into) + } + + fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + writer.write(self.as_ref()).map_err(Into::into) + } +} diff --git a/src/asn1_types/strings/teletexstring.rs b/src/asn1_types/strings/teletexstring.rs new file mode 100644 index 0000000..2f58804 --- /dev/null +++ b/src/asn1_types/strings/teletexstring.rs @@ -0,0 +1,18 @@ +use crate::{asn1_string, TestValidCharset}; +use crate::{Error, Result}; +use alloc::string::String; + +asn1_string!(TeletexString); + +impl<'a> TestValidCharset for TeletexString<'a> { + fn test_valid_charset(i: &[u8]) -> Result<()> { + #[allow(clippy::trivially_copy_pass_by_ref)] + fn is_visible(b: &u8) -> bool { + 0x20 <= *b && *b <= 0x7f + } + if !i.iter().all(is_visible) { + return Err(Error::StringInvalidCharset); + } + Ok(()) + } +} diff --git a/src/asn1_types/strings/universalstring.rs b/src/asn1_types/strings/universalstring.rs new file mode 100644 index 0000000..9006362 --- /dev/null +++ b/src/asn1_types/strings/universalstring.rs @@ -0,0 +1,137 @@ +// do not use the `asn1_string` macro, since types are not the same +// X.680 section 37.6 and X.690 section 8.21.7 + +use crate::*; +use alloc::borrow::Cow; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use core::convert::TryFrom; +use core::iter::FromIterator; + +/// ASN.1 `UniversalString` type +/// +/// Note: parsing a `UniversalString` allocates memory since the UCS-4 to UTF-8 conversion requires a memory allocation. +#[derive(Debug, PartialEq, Eq)] +pub struct UniversalString<'a> { + pub(crate) data: Cow<'a, str>, +} + +impl<'a> UniversalString<'a> { + pub const fn new(s: &'a str) -> Self { + UniversalString { + data: Cow::Borrowed(s), + } + } + + pub fn string(&self) -> String { + self.data.to_string() + } +} + +impl<'a> AsRef<str> for UniversalString<'a> { + fn as_ref(&self) -> &str { + &self.data + } +} + +impl<'a> From<&'a str> for UniversalString<'a> { + fn from(s: &'a str) -> Self { + Self::new(s) + } +} + +impl From<String> for UniversalString<'_> { + fn from(s: String) -> Self { + Self { + data: alloc::borrow::Cow::Owned(s), + } + } +} + +impl<'a> TryFrom<Any<'a>> for UniversalString<'a> { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<UniversalString<'a>> { + TryFrom::try_from(&any) + } +} + +impl<'a, 'b> TryFrom<&'b Any<'a>> for UniversalString<'a> { + type Error = Error; + + fn try_from(any: &'b Any<'a>) -> Result<UniversalString<'a>> { + any.tag().assert_eq(Self::TAG)?; + + if any.data.len() % 4 != 0 { + return Err(Error::StringInvalidCharset); + } + + // read slice as big-endian UCS-4 string + let v = &any + .data + .chunks(4) + .map(|s| match s { + [a, b, c, d] => { + let u32_val = ((*a as u32) << 24) + | ((*b as u32) << 16) + | ((*c as u32) << 8) + | (*d as u32); + char::from_u32(u32_val) + } + _ => unreachable!(), + }) + .collect::<Option<Vec<_>>>() + .ok_or(Error::StringInvalidCharset)?; + + let s = String::from_iter(v); + let data = Cow::Owned(s); + + Ok(UniversalString { data }) + } +} + +impl<'a> CheckDerConstraints for UniversalString<'a> { + fn check_constraints(any: &Any) -> Result<()> { + any.header.assert_primitive()?; + Ok(()) + } +} + +impl DerAutoDerive for UniversalString<'_> {} + +impl<'a> Tagged for UniversalString<'a> { + const TAG: Tag = Tag::UniversalString; +} + +#[cfg(feature = "std")] +impl ToDer for UniversalString<'_> { + fn to_der_len(&self) -> Result<usize> { + // UCS-4: 4 bytes per character + let sz = self.data.as_bytes().len() * 4; + if sz < 127 { + // 1 (class+tag) + 1 (length) + len + Ok(2 + sz) + } else { + // 1 (class+tag) + n (length) + len + let n = Length::Definite(sz).to_der_len()?; + Ok(1 + n + sz) + } + } + + fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let header = Header::new( + Class::Universal, + false, + Self::TAG, + Length::Definite(self.data.as_bytes().len() * 4), + ); + header.write_der_header(writer).map_err(Into::into) + } + + fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + self.data + .chars() + .try_for_each(|c| writer.write(&(c as u32).to_be_bytes()[..]).map(|_| ()))?; + Ok(self.data.as_bytes().len() * 4) + } +} diff --git a/src/asn1_types/strings/utf8string.rs b/src/asn1_types/strings/utf8string.rs new file mode 100644 index 0000000..0bf87d4 --- /dev/null +++ b/src/asn1_types/strings/utf8string.rs @@ -0,0 +1,13 @@ +use crate::asn1_string; +use crate::Result; +use crate::TestValidCharset; +use alloc::string::String; + +asn1_string!(Utf8String); + +impl<'a> TestValidCharset for Utf8String<'a> { + fn test_valid_charset(i: &[u8]) -> Result<()> { + let _ = core::str::from_utf8(i)?; + Ok(()) + } +} diff --git a/src/asn1_types/strings/videotexstring.rs b/src/asn1_types/strings/videotexstring.rs new file mode 100644 index 0000000..51c5a46 --- /dev/null +++ b/src/asn1_types/strings/videotexstring.rs @@ -0,0 +1,19 @@ +use crate::{asn1_string, TestValidCharset}; +use crate::{Error, Result}; +use alloc::string::String; + +asn1_string!(VideotexString); + +impl<'a> TestValidCharset for VideotexString<'a> { + fn test_valid_charset(i: &[u8]) -> Result<()> { + #[allow(clippy::trivially_copy_pass_by_ref)] + fn is_visible(b: &u8) -> bool { + // XXX + 0x20 <= *b && *b <= 0x7f + } + if !i.iter().all(is_visible) { + return Err(Error::StringInvalidCharset); + } + Ok(()) + } +} diff --git a/src/asn1_types/strings/visiblestring.rs b/src/asn1_types/strings/visiblestring.rs new file mode 100644 index 0000000..2b141dc --- /dev/null +++ b/src/asn1_types/strings/visiblestring.rs @@ -0,0 +1,18 @@ +use crate::{asn1_string, TestValidCharset}; +use crate::{Error, Result}; +use alloc::string::String; + +asn1_string!(VisibleString); + +impl<'a> TestValidCharset for VisibleString<'a> { + fn test_valid_charset(i: &[u8]) -> Result<()> { + #[allow(clippy::trivially_copy_pass_by_ref)] + fn is_visible(b: &u8) -> bool { + 0x20 <= *b && *b <= 0x7f + } + if !i.iter().all(is_visible) { + return Err(Error::StringInvalidCharset); + } + Ok(()) + } +} |