Repository: alex/rust-asn1 (language: rust)

Issue:
Support UniversalString type
UniversalString is allowed in `DisplayText` in RFC 5280. Validating the bytes but only having a method to get bytes (rather than providing a `str`) would be sufficient for the cryptography use case.


Discussion / hints:

Fix patch:
diff --git a/fuzz/fuzz_targets/fuzz_asn1_parse.rs b/fuzz/fuzz_targets/fuzz_asn1_parse.rs
index 12b2128..6e361b6 100644
--- a/fuzz/fuzz_targets/fuzz_asn1_parse.rs
+++ b/fuzz/fuzz_targets/fuzz_asn1_parse.rs
@@ -15,6 +15,7 @@ fuzz_target!(|data: &[u8]| {
         d.read_element::<&[u8]>()?;
         d.read_element::<asn1::PrintableString>()?;
         d.read_element::<asn1::BMPString>()?;
+        d.read_element::<asn1::UniversalString>()?;
         d.read_element::<asn1::ObjectIdentifier>()?;
         d.read_element::<asn1::BitString>()?;
         d.read_element::<asn1::UtcTime>()?;
diff --git a/src/lib.rs b/src/lib.rs
index 6650517..1e8719b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -113,8 +113,8 @@ pub use crate::parser::{
 pub use crate::types::{
     Asn1Readable, Asn1Writable, BMPString, BigUint, Choice1, Choice2, Choice3, Enumerated,
     GeneralizedTime, IA5String, Null, PrintableString, Sequence, SequenceOf, SequenceOfWriter,
-    SequenceWriter, SetOf, SetOfWriter, SimpleAsn1Readable, SimpleAsn1Writable, Tlv, UtcTime,
-    Utf8String, VisibleString,
+    SequenceWriter, SetOf, SetOfWriter, SimpleAsn1Readable, SimpleAsn1Writable, Tlv,
+    UniversalString, UtcTime, Utf8String, VisibleString,
 };
 #[cfg(feature = "const-generics")]
 pub use crate::types::{Explicit, Implicit};
diff --git a/src/parser.rs b/src/parser.rs
index 409c258..829e514 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -269,7 +269,8 @@ mod tests {
     use crate::{
         BMPString, BigUint, BitString, Choice1, Choice2, Choice3, Enumerated, GeneralizedTime,
         IA5String, ObjectIdentifier, ParseError, ParseErrorKind, ParseLocation, ParseResult,
-        PrintableString, Sequence, SequenceOf, SetOf, Tlv, UtcTime, Utf8String, VisibleString,
+        PrintableString, Sequence, SequenceOf, SetOf, Tlv, UniversalString, UtcTime, Utf8String,
+        VisibleString,
     };
     #[cfg(feature = "const-generics")]
     use crate::{Explicit, Implicit};
@@ -731,6 +732,36 @@ mod tests {
         ]);
     }
 
+    #[test]
+    fn test_parse_universalstring() {
+        assert_parses::<UniversalString>(&[
+            (
+                Ok(UniversalString::new(b"\x00\x00\x00a\x00\x00\x00b\x00\x00\x00c").unwrap()),
+                b"\x1c\x0c\x00\x00\x00a\x00\x00\x00b\x00\x00\x00c",
+            ),
+            (
+                Err(ParseError::new(ParseErrorKind::InvalidValue)),
+                b"\x1c\x01a",
+            ),
+            (
+                Err(ParseError::new(ParseErrorKind::InvalidValue)),
+                b"\x1c\x02ab",
+            ),
+            (
+                Err(ParseError::new(ParseErrorKind::InvalidValue)),
+                b"\x1c\x03abc",
+            ),
+            (
+                Err(ParseError::new(ParseErrorKind::InvalidValue)),
+                b"\x1c\x03abc",
+            ),
+            (
+                Err(ParseError::new(ParseErrorKind::InvalidValue)),
+                b"\x1c\x04\x96\x8c\xeaU",
+            ),
+        ]);
+    }
+
     #[test]
     fn test_parse_utctime() {
         assert_parses::<UtcTime>(&[
diff --git a/src/types.rs b/src/types.rs
index 5cd1c76..65c827f 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -407,7 +407,7 @@ impl<'a> BMPString<'a> {
 
         for r in core::char::decode_utf16(
             b.chunks_exact(2)
-                .map(|v| ((v[0] as u16) << 8) | (v[1] as u16)),
+                .map(|v| u16::from_be_bytes(v.try_into().unwrap())),
         ) {
             if r.is_err() {
                 return false;
@@ -435,6 +435,56 @@ impl<'a> SimpleAsn1Writable<'a> for BMPString<'a> {
     }
 }
 
+/// Type for use with `Parser.read_element` and `Writer.write_element` for
+/// handling ASN.1 `UniversalString`. A `UniversalString` contains encoded
+/// (UTF-32-BE) bytes which are known to be valid.
+#[derive(Debug, PartialEq, Clone)]
+pub struct UniversalString<'a>(&'a [u8]);
+
+impl<'a> UniversalString<'a> {
+    pub fn new(b: &'a [u8]) -> Option<UniversalString<'a>> {
+        if UniversalString::verify(b) {
+            Some(UniversalString(b))
+        } else {
+            None
+        }
+    }
+
+    fn verify(b: &[u8]) -> bool {
+        if b.len() % 4 != 0 {
+            return false;
+        }
+
+        for r in b
+            .chunks_exact(4)
+            .map(|v| u32::from_be_bytes(v.try_into().unwrap()))
+        {
+            if core::char::from_u32(r).is_none() {
+                return false;
+            }
+        }
+
+        true
+    }
+
+    pub fn as_utf32_be_bytes(&self) -> &'a [u8] {
+        self.0
+    }
+}
+
+impl<'a> SimpleAsn1Readable<'a> for UniversalString<'a> {
+    const TAG: u8 = 0x1c;
+    fn parse_data(data: &'a [u8]) -> ParseResult<Self> {
+        UniversalString::new(data).ok_or_else(|| ParseError::new(ParseErrorKind::InvalidValue))
+    }
+}
+impl<'a> SimpleAsn1Writable<'a> for UniversalString<'a> {
+    const TAG: u8 = 0x1c;
+    fn write_data(&self, dest: &mut Vec<u8>) {
+        dest.extend_from_slice(self.as_utf32_be_bytes());
+    }
+}
+
 fn validate_integer(data: &[u8], signed: bool) -> ParseResult<()> {
     if data.is_empty() {
         return Err(ParseError::new(ParseErrorKind::InvalidValue));
diff --git a/src/writer.rs b/src/writer.rs
index c4a7253..e021540 100644
--- a/src/writer.rs
+++ b/src/writer.rs
@@ -134,8 +134,8 @@ mod tests {
     use crate::{
         parse_single, BMPString, BigUint, BitString, Choice1, Choice2, Choice3, Enumerated,
         GeneralizedTime, IA5String, ObjectIdentifier, OwnedBitString, PrintableString, Sequence,
-        SequenceOf, SequenceOfWriter, SequenceWriter, SetOf, SetOfWriter, Tlv, UtcTime, Utf8String,
-        VisibleString,
+        SequenceOf, SequenceOfWriter, SequenceWriter, SetOf, SetOfWriter, Tlv, UniversalString,
+        UtcTime, Utf8String, VisibleString,
     };
     #[cfg(feature = "const-generics")]
     use crate::{Explicit, Implicit};
@@ -245,6 +245,14 @@ mod tests {
         )])
     }
 
+    #[test]
+    fn test_write_universalstring() {
+        assert_writes::<UniversalString>(&[(
+            UniversalString::new(b"\x00\x00\x00a\x00\x00\x00b\x00\x00\x00c").unwrap(),
+            b"\x1c\x0c\x00\x00\x00a\x00\x00\x00b\x00\x00\x00c",
+        )])
+    }
+
     #[test]
     fn test_write_i64() {
         assert_writes::<i64>(&[


Test patch: