summaryrefslogtreecommitdiff
path: root/src/asn1_types/strings
diff options
context:
space:
mode:
Diffstat (limited to 'src/asn1_types/strings')
-rw-r--r--src/asn1_types/strings/bmpstring.rs132
-rw-r--r--src/asn1_types/strings/generalstring.rs14
-rw-r--r--src/asn1_types/strings/graphicstring.rs14
-rw-r--r--src/asn1_types/strings/ia5string.rs14
-rw-r--r--src/asn1_types/strings/numericstring.rs18
-rw-r--r--src/asn1_types/strings/printablestring.rs35
-rw-r--r--src/asn1_types/strings/str.rs67
-rw-r--r--src/asn1_types/strings/string.rs64
-rw-r--r--src/asn1_types/strings/teletexstring.rs18
-rw-r--r--src/asn1_types/strings/universalstring.rs137
-rw-r--r--src/asn1_types/strings/utf8string.rs13
-rw-r--r--src/asn1_types/strings/videotexstring.rs19
-rw-r--r--src/asn1_types/strings/visiblestring.rs18
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(())
+ }
+}