diff options
Diffstat (limited to 'src/asn1_types')
51 files changed, 7282 insertions, 0 deletions
diff --git a/src/asn1_types/any.rs b/src/asn1_types/any.rs new file mode 100644 index 0000000..8628d7d --- /dev/null +++ b/src/asn1_types/any.rs @@ -0,0 +1,429 @@ +use crate::ber::*; +use crate::*; +use alloc::borrow::Cow; +use alloc::string::String; +use core::convert::{TryFrom, TryInto}; + +/// The `Any` object is not strictly an ASN.1 type, but holds a generic description of any object +/// that could be encoded. +/// +/// It contains a header, and either a reference to or owned data for the object content. +/// +/// Note: this type is only provided in **borrowed** version (*i.e.* it cannot own the inner data). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Any<'a> { + /// The object header + pub header: Header<'a>, + /// The object contents + pub data: &'a [u8], +} + +impl<'a> Any<'a> { + /// Create a new `Any` from BER/DER header and content + #[inline] + pub const fn new(header: Header<'a>, data: &'a [u8]) -> Self { + Any { header, data } + } + + /// Create a new `Any` from a tag, and BER/DER content + #[inline] + pub const fn from_tag_and_data(tag: Tag, data: &'a [u8]) -> Self { + let constructed = matches!(tag, Tag::Sequence | Tag::Set); + Any { + header: Header { + tag, + constructed, + class: Class::Universal, + length: Length::Definite(data.len()), + raw_tag: None, + }, + data, + } + } + + /// Return the `Class` of this object + #[inline] + pub const fn class(&self) -> Class { + self.header.class + } + + /// Update the class of the current object + #[inline] + pub fn with_class(self, class: Class) -> Self { + Any { + header: self.header.with_class(class), + ..self + } + } + + /// Return the `Tag` of this object + #[inline] + pub const fn tag(&self) -> Tag { + self.header.tag + } + + /// Update the tag of the current object + #[inline] + pub fn with_tag(self, tag: Tag) -> Self { + Any { + header: self.header.with_tag(tag), + data: self.data, + } + } + + /// Get the bytes representation of the *content* + #[inline] + pub fn as_bytes(&'a self) -> &'a [u8] { + self.data + } + + #[inline] + pub fn parse_ber<T>(&'a self) -> ParseResult<'a, T> + where + T: FromBer<'a>, + { + T::from_ber(self.data) + } + + /// Parse a BER value and apply the provided parsing function to content + /// + /// After parsing, the sequence object and header are discarded. + pub fn from_ber_and_then<F, T, E>( + class: Class, + tag: u32, + bytes: &'a [u8], + op: F, + ) -> ParseResult<'a, T, E> + where + F: FnOnce(&'a [u8]) -> ParseResult<T, E>, + E: From<Error>, + { + let (rem, any) = Any::from_ber(bytes).map_err(Err::convert)?; + any.tag() + .assert_eq(Tag(tag)) + .map_err(|e| nom::Err::Error(e.into()))?; + any.class() + .assert_eq(class) + .map_err(|e| nom::Err::Error(e.into()))?; + let (_, res) = op(any.data)?; + Ok((rem, res)) + } + + /// Parse a DER value and apply the provided parsing function to content + /// + /// After parsing, the sequence object and header are discarded. + pub fn from_der_and_then<F, T, E>( + class: Class, + tag: u32, + bytes: &'a [u8], + op: F, + ) -> ParseResult<'a, T, E> + where + F: FnOnce(&'a [u8]) -> ParseResult<T, E>, + E: From<Error>, + { + let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?; + any.tag() + .assert_eq(Tag(tag)) + .map_err(|e| nom::Err::Error(e.into()))?; + any.class() + .assert_eq(class) + .map_err(|e| nom::Err::Error(e.into()))?; + let (_, res) = op(any.data)?; + Ok((rem, res)) + } + + #[inline] + pub fn parse_der<T>(&'a self) -> ParseResult<'a, T> + where + T: FromDer<'a>, + { + T::from_der(self.data) + } + + /// Get the content following a BER header + #[inline] + pub fn parse_ber_content<'i>(i: &'i [u8], header: &'_ Header) -> ParseResult<'i, &'i [u8]> { + header.parse_ber_content(i) + } + + /// Get the content following a DER header + #[inline] + pub fn parse_der_content<'i>(i: &'i [u8], header: &'_ Header) -> ParseResult<'i, &'i [u8]> { + header.assert_definite()?; + DerParser::get_object_content(i, header, 8) + } +} + +macro_rules! impl_any_into { + (IMPL $sname:expr, $fn_name:ident => $ty:ty, $asn1:expr) => { + #[doc = "Attempt to convert object to `"] + #[doc = $sname] + #[doc = "` (ASN.1 type: `"] + #[doc = $asn1] + #[doc = "`)."] + pub fn $fn_name(self) -> Result<$ty> { + self.try_into() + } + }; + ($fn_name:ident => $ty:ty, $asn1:expr) => { + impl_any_into! { + IMPL stringify!($ty), $fn_name => $ty, $asn1 + } + }; +} + +macro_rules! impl_any_as { + (IMPL $sname:expr, $fn_name:ident => $ty:ty, $asn1:expr) => { + #[doc = "Attempt to create ASN.1 type `"] + #[doc = $asn1] + #[doc = "` from this object."] + #[inline] + pub fn $fn_name(&self) -> Result<$ty> { + TryFrom::try_from(self) + } + }; + ($fn_name:ident => $ty:ty, $asn1:expr) => { + impl_any_as! { + IMPL stringify!($ty), $fn_name => $ty, $asn1 + } + }; +} + +impl<'a> Any<'a> { + impl_any_into!(bitstring => BitString<'a>, "BIT STRING"); + impl_any_into!(bmpstring => BmpString<'a>, "BmpString"); + impl_any_into!(bool => bool, "BOOLEAN"); + impl_any_into!(boolean => Boolean, "BOOLEAN"); + impl_any_into!(embedded_pdv => EmbeddedPdv<'a>, "EMBEDDED PDV"); + impl_any_into!(enumerated => Enumerated, "ENUMERATED"); + impl_any_into!(generalizedtime => GeneralizedTime, "GeneralizedTime"); + impl_any_into!(generalstring => GeneralString<'a>, "GeneralString"); + impl_any_into!(graphicstring => GraphicString<'a>, "GraphicString"); + impl_any_into!(i8 => i8, "INTEGER"); + impl_any_into!(i16 => i16, "INTEGER"); + impl_any_into!(i32 => i32, "INTEGER"); + impl_any_into!(i64 => i64, "INTEGER"); + impl_any_into!(i128 => i128, "INTEGER"); + impl_any_into!(ia5string => Ia5String<'a>, "IA5String"); + impl_any_into!(integer => Integer<'a>, "INTEGER"); + impl_any_into!(null => Null, "NULL"); + impl_any_into!(numericstring => NumericString<'a>, "NumericString"); + impl_any_into!(objectdescriptor => ObjectDescriptor<'a>, "ObjectDescriptor"); + impl_any_into!(octetstring => OctetString<'a>, "OCTET STRING"); + impl_any_into!(oid => Oid<'a>, "OBJECT IDENTIFIER"); + /// Attempt to convert object to `Oid` (ASN.1 type: `RELATIVE-OID`). + pub fn relative_oid(self) -> Result<Oid<'a>> { + self.header.assert_tag(Tag::RelativeOid)?; + let asn1 = Cow::Borrowed(self.data); + Ok(Oid::new_relative(asn1)) + } + impl_any_into!(printablestring => PrintableString<'a>, "PrintableString"); + // XXX REAL + impl_any_into!(sequence => Sequence<'a>, "SEQUENCE"); + impl_any_into!(set => Set<'a>, "SET"); + impl_any_into!(str => &'a str, "UTF8String"); + impl_any_into!(string => String, "UTF8String"); + impl_any_into!(teletexstring => TeletexString<'a>, "TeletexString"); + impl_any_into!(u8 => u8, "INTEGER"); + impl_any_into!(u16 => u16, "INTEGER"); + impl_any_into!(u32 => u32, "INTEGER"); + impl_any_into!(u64 => u64, "INTEGER"); + impl_any_into!(u128 => u128, "INTEGER"); + impl_any_into!(universalstring => UniversalString<'a>, "UniversalString"); + impl_any_into!(utctime => UtcTime, "UTCTime"); + impl_any_into!(utf8string => Utf8String<'a>, "UTF8String"); + impl_any_into!(videotexstring => VideotexString<'a>, "VideotexString"); + impl_any_into!(visiblestring => VisibleString<'a>, "VisibleString"); + + impl_any_as!(as_bitstring => BitString, "BITSTRING"); + impl_any_as!(as_bool => bool, "BOOLEAN"); + impl_any_as!(as_boolean => Boolean, "BOOLEAN"); + impl_any_as!(as_embedded_pdv => EmbeddedPdv, "EMBEDDED PDV"); + impl_any_as!(as_endofcontent => EndOfContent, "END OF CONTENT (not a real ASN.1 type)"); + impl_any_as!(as_enumerated => Enumerated, "ENUMERATED"); + impl_any_as!(as_generalizedtime => GeneralizedTime, "GeneralizedTime"); + impl_any_as!(as_generalstring => GeneralizedTime, "GeneralString"); + impl_any_as!(as_graphicstring => GraphicString, "GraphicString"); + impl_any_as!(as_i8 => i8, "INTEGER"); + impl_any_as!(as_i16 => i16, "INTEGER"); + impl_any_as!(as_i32 => i32, "INTEGER"); + impl_any_as!(as_i64 => i64, "INTEGER"); + impl_any_as!(as_i128 => i128, "INTEGER"); + impl_any_as!(as_ia5string => Ia5String, "IA5String"); + impl_any_as!(as_integer => Integer, "INTEGER"); + impl_any_as!(as_null => Null, "NULL"); + impl_any_as!(as_numericstring => NumericString, "NumericString"); + impl_any_as!(as_objectdescriptor => ObjectDescriptor, "OBJECT IDENTIFIER"); + impl_any_as!(as_octetstring => OctetString, "OCTET STRING"); + impl_any_as!(as_oid => Oid, "OBJECT IDENTIFIER"); + /// Attempt to create ASN.1 type `RELATIVE-OID` from this object. + pub fn as_relative_oid(&self) -> Result<Oid<'a>> { + self.header.assert_tag(Tag::RelativeOid)?; + let asn1 = Cow::Borrowed(self.data); + Ok(Oid::new_relative(asn1)) + } + impl_any_as!(as_printablestring => PrintableString, "PrintableString"); + impl_any_as!(as_sequence => Sequence, "SEQUENCE"); + impl_any_as!(as_set => Set, "SET"); + impl_any_as!(as_str => &str, "UTF8String"); + impl_any_as!(as_string => String, "UTF8String"); + impl_any_as!(as_teletexstring => TeletexString, "TeletexString"); + impl_any_as!(as_u8 => u8, "INTEGER"); + impl_any_as!(as_u16 => u16, "INTEGER"); + impl_any_as!(as_u32 => u32, "INTEGER"); + impl_any_as!(as_u64 => u64, "INTEGER"); + impl_any_as!(as_u128 => u128, "INTEGER"); + impl_any_as!(as_universalstring => UniversalString, "UniversalString"); + impl_any_as!(as_utctime => UtcTime, "UTCTime"); + impl_any_as!(as_utf8string => Utf8String, "UTF8String"); + impl_any_as!(as_videotexstring => VideotexString, "VideotexString"); + impl_any_as!(as_visiblestring => VisibleString, "VisibleString"); + + /// Attempt to create an `Option<T>` from this object. + pub fn as_optional<'b, T>(&'b self) -> Result<Option<T>> + where + T: TryFrom<&'b Any<'a>, Error = Error>, + 'a: 'b, + { + match TryFrom::try_from(self) { + Ok(t) => Ok(Some(t)), + Err(Error::UnexpectedTag { .. }) => Ok(None), + Err(e) => Err(e), + } + } + + /// Attempt to create a tagged value (EXPLICIT) from this object. + pub fn as_tagged_explicit<T, E, const CLASS: u8, const TAG: u32>( + &self, + ) -> Result<TaggedValue<T, E, Explicit, CLASS, TAG>, E> + where + T: FromBer<'a, E>, + E: From<Error>, + { + TryFrom::try_from(self) + } + + /// Attempt to create a tagged value (IMPLICIT) from this object. + pub fn as_tagged_implicit<T, E, const CLASS: u8, const TAG: u32>( + &self, + ) -> Result<TaggedValue<T, E, Implicit, CLASS, TAG>, E> + where + T: TryFrom<Any<'a>, Error = E>, + T: Tagged, + E: From<Error>, + { + TryFrom::try_from(self) + } +} + +impl<'a> FromBer<'a> for Any<'a> { + fn from_ber(bytes: &'a [u8]) -> ParseResult<Self> { + let (i, header) = Header::from_ber(bytes)?; + let (i, data) = BerParser::get_object_content(i, &header, MAX_RECURSION)?; + Ok((i, Any { header, data })) + } +} + +impl<'a> FromDer<'a> for Any<'a> { + fn from_der(bytes: &'a [u8]) -> ParseResult<Self> { + let (i, header) = Header::from_der(bytes)?; + // X.690 section 10.1: The definite form of length encoding shall be used + header.length.assert_definite()?; + let (i, data) = DerParser::get_object_content(i, &header, MAX_RECURSION)?; + Ok((i, Any { header, data })) + } +} + +impl CheckDerConstraints for Any<'_> { + fn check_constraints(any: &Any) -> Result<()> { + any.header.length().assert_definite()?; + // if len < 128, must use short form (10.1: minimum number of octets) + Ok(()) + } +} + +impl DerAutoDerive for Any<'_> {} + +impl DynTagged for Any<'_> { + fn tag(&self) -> Tag { + self.tag() + } +} + +// impl<'a> ToStatic for Any<'a> { +// type Owned = Any<'static>; + +// fn to_static(&self) -> Self::Owned { +// Any { +// header: self.header.to_static(), +// data: Cow::Owned(self.data.to_vec()), +// } +// } +// } + +#[cfg(feature = "std")] +impl ToDer for Any<'_> { + fn to_der_len(&self) -> Result<usize> { + let hdr_len = self.header.to_der_len()?; + Ok(hdr_len + self.data.len()) + } + + fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + // create fake header to have correct length + let header = Header::new( + self.header.class, + self.header.constructed, + self.header.tag, + Length::Definite(self.data.len()), + ); + let sz = header.write_der_header(writer)?; + Ok(sz) + } + + fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + writer.write(self.data).map_err(Into::into) + } + + /// Similar to using `to_der`, but uses header without computing length value + fn write_der_raw(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let sz = self.header.write_der_header(writer)?; + let sz = sz + writer.write(self.data)?; + Ok(sz) + } +} + +#[cfg(test)] +mod tests { + use crate::*; + use hex_literal::hex; + + #[test] + fn methods_any() { + let header = Header::new_simple(Tag::Integer); + let any = Any::new(header, &[]) + .with_class(Class::ContextSpecific) + .with_tag(Tag(0)); + assert_eq!(any.as_bytes(), &[]); + + let input = &hex! {"80 03 02 01 01"}; + let (_, any) = Any::from_ber(input).expect("parsing failed"); + + let (_, r) = any.parse_ber::<Integer>().expect("parse_ber failed"); + assert_eq!(r.as_u32(), Ok(1)); + let (_, r) = any.parse_der::<Integer>().expect("parse_der failed"); + assert_eq!(r.as_u32(), Ok(1)); + + let header = &any.header; + let (_, content) = Any::parse_ber_content(&input[2..], header).unwrap(); + assert_eq!(content.len(), 3); + let (_, content) = Any::parse_der_content(&input[2..], header).unwrap(); + assert_eq!(content.len(), 3); + + let (_, any) = Any::from_der(&input[2..]).unwrap(); + Any::check_constraints(&any).unwrap(); + assert_eq!(<Any as DynTagged>::tag(&any), any.tag()); + let int = any.integer().unwrap(); + assert_eq!(int.as_u16(), Ok(1)); + } +} diff --git a/src/asn1_types/bitstring.rs b/src/asn1_types/bitstring.rs new file mode 100644 index 0000000..fad03cf --- /dev/null +++ b/src/asn1_types/bitstring.rs @@ -0,0 +1,157 @@ +use crate::*; +use alloc::borrow::Cow; +#[cfg(feature = "bits")] +use bitvec::{order::Msb0, slice::BitSlice}; +use core::convert::TryFrom; + +/// ASN.1 `BITSTRING` type +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BitString<'a> { + pub unused_bits: u8, + pub data: Cow<'a, [u8]>, +} + +impl<'a> BitString<'a> { + // Length must be >= 1 (first byte is number of ignored bits) + pub const fn new(unused_bits: u8, s: &'a [u8]) -> Self { + BitString { + unused_bits, + data: Cow::Borrowed(s), + } + } + + /// Test if bit `bitnum` is set + pub fn is_set(&self, bitnum: usize) -> bool { + let byte_pos = bitnum / 8; + if byte_pos >= self.data.len() { + return false; + } + let b = 7 - (bitnum % 8); + (self.data[byte_pos] & (1 << b)) != 0 + } + + /// Constructs a shared `&BitSlice` reference over the object data. + #[cfg(feature = "bits")] + pub fn as_bitslice(&self) -> Option<&BitSlice<u8, Msb0>> { + BitSlice::<_, Msb0>::try_from_slice(&self.data).ok() + } +} + +impl<'a> AsRef<[u8]> for BitString<'a> { + fn as_ref(&self) -> &[u8] { + &self.data + } +} + +impl<'a> TryFrom<Any<'a>> for BitString<'a> { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<BitString<'a>> { + TryFrom::try_from(&any) + } +} + +// non-consuming version +impl<'a, 'b> TryFrom<&'b Any<'a>> for BitString<'a> { + type Error = Error; + + fn try_from(any: &'b Any<'a>) -> Result<BitString<'a>> { + any.tag().assert_eq(Self::TAG)?; + if any.data.is_empty() { + return Err(Error::InvalidLength); + } + let s = any.data; + let (unused_bits, data) = (s[0], Cow::Borrowed(&s[1..])); + Ok(BitString { unused_bits, data }) + } +} + +impl<'a> CheckDerConstraints for BitString<'a> { + fn check_constraints(any: &Any) -> Result<()> { + // X.690 section 10.2 + any.header.assert_primitive()?; + // Check that padding bits are all 0 (X.690 section 11.2.1) + match any.data.len() { + 0 => Err(Error::InvalidLength), + 1 => { + // X.690 section 11.2.2 Note 2 + if any.data[0] == 0 { + Ok(()) + } else { + Err(Error::InvalidLength) + } + } + len => { + let unused_bits = any.data[0]; + let last_byte = any.data[len - 1]; + if last_byte.trailing_zeros() < unused_bits as u32 { + return Err(Error::DerConstraintFailed(DerConstraint::UnusedBitsNotZero)); + } + + Ok(()) + } + } + } +} + +impl DerAutoDerive for BitString<'_> {} + +impl<'a> Tagged for BitString<'a> { + const TAG: Tag = Tag::BitString; +} + +#[cfg(feature = "std")] +impl ToDer for BitString<'_> { + fn to_der_len(&self) -> Result<usize> { + let sz = self.data.len(); + if sz < 127 { + // 1 (class+tag) + 1 (length) + 1 (unused bits) + len + Ok(3 + sz) + } else { + // 1 (class+tag) + n (length) + 1 (unused bits) + len + let n = Length::Definite(sz + 1).to_der_len()?; + Ok(2 + 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(1 + self.data.len()), + ); + header.write_der_header(writer).map_err(Into::into) + } + + fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let sz = writer.write(&[self.unused_bits])?; + let sz = sz + writer.write(&self.data)?; + Ok(sz) + } +} + +#[cfg(test)] +mod tests { + use super::BitString; + + #[test] + fn test_bitstring_is_set() { + let obj = BitString::new(0, &[0x0f, 0x00, 0x40]); + assert!(!obj.is_set(0)); + assert!(obj.is_set(7)); + assert!(!obj.is_set(9)); + assert!(obj.is_set(17)); + } + + #[cfg(feature = "bits")] + #[test] + fn test_bitstring_to_bitvec() { + let obj = BitString::new(0, &[0x0f, 0x00, 0x40]); + let bv = obj.as_bitslice().expect("could not get bitslice"); + assert_eq!(bv.get(0).as_deref(), Some(&false)); + assert_eq!(bv.get(7).as_deref(), Some(&true)); + assert_eq!(bv.get(9).as_deref(), Some(&false)); + assert_eq!(bv.get(17).as_deref(), Some(&true)); + } +} diff --git a/src/asn1_types/boolean.rs b/src/asn1_types/boolean.rs new file mode 100644 index 0000000..ed620e4 --- /dev/null +++ b/src/asn1_types/boolean.rs @@ -0,0 +1,147 @@ +use crate::*; +use core::convert::TryFrom; + +/// ASN.1 `BOOLEAN` type +/// +/// BER objects consider any non-zero value as `true`, and `0` as `false`. +/// +/// DER objects must use value `0x0` (`false`) or `0xff` (`true`). +#[derive(Debug, PartialEq, Eq)] +pub struct Boolean { + pub value: u8, +} + +impl Boolean { + /// `BOOLEAN` object for value `false` + pub const FALSE: Boolean = Boolean::new(0); + /// `BOOLEAN` object for value `true` + pub const TRUE: Boolean = Boolean::new(0xff); + + /// Create a new `Boolean` from the provided logical value. + #[inline] + pub const fn new(value: u8) -> Self { + Boolean { value } + } + + /// Return the `bool` value from this object. + #[inline] + pub const fn bool(&self) -> bool { + self.value != 0 + } +} + +impl<'a> TryFrom<Any<'a>> for Boolean { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<Boolean> { + TryFrom::try_from(&any) + } +} + +// non-consuming version +impl<'a, 'b> TryFrom<&'b Any<'a>> for Boolean { + type Error = Error; + + fn try_from(any: &'b Any<'a>) -> Result<Boolean> { + any.tag().assert_eq(Self::TAG)?; + // X.690 section 8.2.1: + // The encoding of a boolean value shall be primitive. The contents octets shall consist of a single octet + if any.header.length != Length::Definite(1) { + return Err(Error::InvalidLength); + } + let value = any.data[0]; + Ok(Boolean { value }) + } +} + +impl CheckDerConstraints for Boolean { + fn check_constraints(any: &Any) -> Result<()> { + let c = any.data[0]; + // X.690 section 11.1 + if !(c == 0 || c == 0xff) { + return Err(Error::DerConstraintFailed(DerConstraint::InvalidBoolean)); + } + Ok(()) + } +} + +impl DerAutoDerive for Boolean {} + +impl Tagged for Boolean { + const TAG: Tag = Tag::Boolean; +} + +#[cfg(feature = "std")] +impl ToDer for Boolean { + fn to_der_len(&self) -> Result<usize> { + // 3 = 1 (tag) + 1 (length) + 1 (value) + Ok(3) + } + + fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + writer.write(&[Self::TAG.0 as u8, 0x01]).map_err(Into::into) + } + + fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let b = if self.value != 0 { 0xff } else { 0x00 }; + writer.write(&[b]).map_err(Into::into) + } + + /// Similar to using `to_der`, but uses header without computing length value + fn write_der_raw(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let sz = writer.write(&[Self::TAG.0 as u8, 0x01, self.value])?; + Ok(sz) + } +} + +impl<'a> TryFrom<Any<'a>> for bool { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<bool> { + TryFrom::try_from(&any) + } +} + +impl<'a, 'b> TryFrom<&'b Any<'a>> for bool { + type Error = Error; + + fn try_from(any: &'b Any<'a>) -> Result<bool> { + any.tag().assert_eq(Self::TAG)?; + let b = Boolean::try_from(any)?; + Ok(b.bool()) + } +} + +impl CheckDerConstraints for bool { + fn check_constraints(any: &Any) -> Result<()> { + let c = any.data[0]; + // X.690 section 11.1 + if !(c == 0 || c == 0xff) { + return Err(Error::DerConstraintFailed(DerConstraint::InvalidBoolean)); + } + Ok(()) + } +} + +impl DerAutoDerive for bool {} + +impl Tagged for bool { + const TAG: Tag = Tag::Boolean; +} + +#[cfg(feature = "std")] +impl ToDer for bool { + fn to_der_len(&self) -> Result<usize> { + // 3 = 1 (tag) + 1 (length) + 1 (value) + Ok(3) + } + + fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + writer.write(&[Self::TAG.0 as u8, 0x01]).map_err(Into::into) + } + + fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let b = if *self { 0xff } else { 0x00 }; + writer.write(&[b]).map_err(Into::into) + } +} diff --git a/src/asn1_types/choice.rs b/src/asn1_types/choice.rs new file mode 100644 index 0000000..4bc01cd --- /dev/null +++ b/src/asn1_types/choice.rs @@ -0,0 +1,21 @@ +use crate::{FromBer, FromDer, Tag, Tagged}; + +pub trait Choice { + /// Is the provided [`Tag`] decodable as a variant of this `CHOICE`? + fn can_decode(tag: Tag) -> bool; +} + +/// This blanket impl allows any [`Tagged`] type to function as a [`Choice`] +/// with a single alternative. +impl<T> Choice for T +where + T: Tagged, +{ + fn can_decode(tag: Tag) -> bool { + T::TAG == tag + } +} + +pub trait BerChoice<'a>: Choice + FromBer<'a> {} + +pub trait DerChoice<'a>: Choice + FromDer<'a> {} diff --git a/src/asn1_types/embedded_pdv.rs b/src/asn1_types/embedded_pdv.rs new file mode 100644 index 0000000..9452856 --- /dev/null +++ b/src/asn1_types/embedded_pdv.rs @@ -0,0 +1,125 @@ +use crate::*; +use core::convert::TryFrom; + +#[derive(Debug, PartialEq, Eq)] +pub struct EmbeddedPdv<'a> { + pub identification: PdvIdentification<'a>, + pub data_value_descriptor: Option<ObjectDescriptor<'a>>, + pub data_value: &'a [u8], +} + +#[derive(Debug, PartialEq, Eq)] +pub enum PdvIdentification<'a> { + Syntaxes { + s_abstract: Oid<'a>, + s_transfer: Oid<'a>, + }, + Syntax(Oid<'a>), + PresentationContextId(Integer<'a>), + ContextNegotiation { + presentation_context_id: Integer<'a>, + presentation_syntax: Oid<'a>, + }, + TransferSyntax(Oid<'a>), + Fixed, +} + +impl<'a> TryFrom<Any<'a>> for EmbeddedPdv<'a> { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<Self> { + TryFrom::try_from(&any) + } +} + +impl<'a, 'b> TryFrom<&'b Any<'a>> for EmbeddedPdv<'a> { + type Error = Error; + + fn try_from(any: &'b Any<'a>) -> Result<Self> { + let data = any.data; + // AUTOMATIC TAGS means all values will be tagged (IMPLICIT) + // [0] -> identification + let (rem, seq0) = + TaggedParser::<Explicit, Any>::parse_ber(Class::ContextSpecific, Tag(0), data)?; + let inner = seq0.inner; + let identification = match inner.tag() { + Tag(0) => { + // syntaxes SEQUENCE { + // abstract OBJECT IDENTIFIER, + // transfer OBJECT IDENTIFIER + // }, + // AUTOMATIC tags -> implicit! Hopefully, Oid does not check tag value! + let (rem, s_abstract) = Oid::from_ber(inner.data)?; + let (_, s_transfer) = Oid::from_ber(rem)?; + PdvIdentification::Syntaxes { + s_abstract, + s_transfer, + } + } + Tag(1) => { + // syntax OBJECT IDENTIFIER + let oid = Oid::new(inner.data.into()); + PdvIdentification::Syntax(oid) + } + Tag(2) => { + // presentation-context-id INTEGER + let i = Integer::new(inner.data); + PdvIdentification::PresentationContextId(i) + } + Tag(3) => { + // context-negotiation SEQUENCE { + // presentation-context-id INTEGER, + // transfer-syntax OBJECT IDENTIFIER + // }, + // AUTOMATIC tags -> implicit! + let (rem, any) = Any::from_ber(inner.data)?; + let presentation_context_id = Integer::new(any.data); + let (_, presentation_syntax) = Oid::from_ber(rem)?; + PdvIdentification::ContextNegotiation { + presentation_context_id, + presentation_syntax, + } + } + Tag(4) => { + // transfer-syntax OBJECT IDENTIFIER + let oid = Oid::new(inner.data.into()); + PdvIdentification::TransferSyntax(oid) + } + Tag(5) => { + // fixed NULL + PdvIdentification::Fixed + } + _ => { + return Err(inner + .tag() + .invalid_value("Invalid identification tag in EMBEDDED PDV")) + } + }; + // [1] -> data-value-descriptor ObjectDescriptor OPTIONAL + // *BUT* WITH COMPONENTS data-value-descriptor ABSENT + // XXX this should be parse_ber? + // let (rem, data_value_descriptor) = + // TaggedOptional::from(1).parse_der(rem, |_, inner| ObjectDescriptor::from_ber(inner))?; + let (rem, data_value_descriptor) = (rem, None); + // [2] -> data-value OCTET STRING + let (_, data_value) = + TaggedParser::<Implicit, &[u8]>::parse_ber(Class::ContextSpecific, Tag(2), rem)?; + let data_value = data_value.inner; + let obj = EmbeddedPdv { + identification, + data_value_descriptor, + data_value, + }; + Ok(obj) + } +} + +impl CheckDerConstraints for EmbeddedPdv<'_> { + fn check_constraints(any: &Any) -> Result<()> { + any.header.length().assert_definite()?; + any.header.assert_constructed()?; + Ok(()) + } +} + +impl DerAutoDerive for EmbeddedPdv<'_> {} diff --git a/src/asn1_types/end_of_content.rs b/src/asn1_types/end_of_content.rs new file mode 100644 index 0000000..4a9d291 --- /dev/null +++ b/src/asn1_types/end_of_content.rs @@ -0,0 +1,55 @@ +use crate::{Any, Error, Result, Tag, Tagged}; +use core::convert::TryFrom; + +/// End-of-contents octets +/// +/// `EndOfContent` is not a BER type, but represents a marked to indicate the end of contents +/// of an object, when the length is `Indefinite` (see X.690 section 8.1.5). +/// +/// This type cannot exist in DER, and so provides no `FromDer`/`ToDer` implementation. +#[derive(Debug)] +pub struct EndOfContent {} + +impl EndOfContent { + pub const fn new() -> Self { + EndOfContent {} + } +} + +impl<'a> TryFrom<Any<'a>> for EndOfContent { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<EndOfContent> { + TryFrom::try_from(&any) + } +} + +impl<'a, 'b> TryFrom<&'b Any<'a>> for EndOfContent { + type Error = Error; + + fn try_from(any: &'b Any<'a>) -> Result<EndOfContent> { + any.tag().assert_eq(Self::TAG)?; + if !any.header.length.is_null() { + return Err(Error::InvalidLength); + } + Ok(EndOfContent {}) + } +} + +impl Tagged for EndOfContent { + const TAG: Tag = Tag::EndOfContent; +} + +// impl ToDer for EndOfContent { +// fn to_der_len(&self) -> Result<usize> { +// Ok(2) +// } + +// fn write_der_header(&self, writer: &mut dyn std::io::Write) -> crate::SerializeResult<usize> { +// writer.write(&[Self::TAG.0 as u8, 0x00]).map_err(Into::into) +// } + +// fn write_der_content(&self, _writer: &mut dyn std::io::Write) -> crate::SerializeResult<usize> { +// Ok(0) +// } +// } diff --git a/src/asn1_types/enumerated.rs b/src/asn1_types/enumerated.rs new file mode 100644 index 0000000..5f843a8 --- /dev/null +++ b/src/asn1_types/enumerated.rs @@ -0,0 +1,72 @@ +use crate::ber::bytes_to_u64; +use crate::*; +use core::convert::TryFrom; + +/// ASN.1 `ENUMERATED` type +/// +/// # Limitations +/// +/// Supported values are limited to 0 .. 2^32 +#[derive(Debug, PartialEq, Eq)] +pub struct Enumerated(pub u32); + +impl Enumerated { + pub const fn new(value: u32) -> Self { + Enumerated(value) + } +} + +impl<'a> TryFrom<Any<'a>> for Enumerated { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<Enumerated> { + TryFrom::try_from(&any) + } +} + +impl<'a, 'b> TryFrom<&'b Any<'a>> for Enumerated { + type Error = Error; + + fn try_from(any: &'b Any<'a>) -> Result<Enumerated> { + any.tag().assert_eq(Self::TAG)?; + any.header.assert_primitive()?; + let res_u64 = bytes_to_u64(any.data)?; + if res_u64 > (<u32>::MAX as u64) { + return Err(Error::IntegerTooLarge); + } + let value = res_u64 as u32; + Ok(Enumerated(value)) + } +} + +impl CheckDerConstraints for Enumerated { + fn check_constraints(any: &Any) -> Result<()> { + any.header.length.assert_definite()?; + Ok(()) + } +} + +impl DerAutoDerive for Enumerated {} + +impl Tagged for Enumerated { + const TAG: Tag = Tag::Enumerated; +} + +#[cfg(feature = "std")] +impl ToDer for Enumerated { + fn to_der_len(&self) -> Result<usize> { + Integer::from(self.0).to_der_len() + } + + fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let i = Integer::from(self.0); + let len = i.data.len(); + let header = Header::new(Class::Universal, false, Self::TAG, Length::Definite(len)); + header.write_der_header(writer).map_err(Into::into) + } + + fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let int = Integer::from(self.0); + int.write_der_content(writer).map_err(Into::into) + } +} diff --git a/src/asn1_types/generalizedtime.rs b/src/asn1_types/generalizedtime.rs new file mode 100644 index 0000000..6e039d8 --- /dev/null +++ b/src/asn1_types/generalizedtime.rs @@ -0,0 +1,303 @@ +use crate::datetime::decode_decimal; +use crate::*; +use alloc::format; +use alloc::string::String; +use core::convert::TryFrom; +use core::fmt; +#[cfg(feature = "datetime")] +use time::OffsetDateTime; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct GeneralizedTime(pub ASN1DateTime); + +impl GeneralizedTime { + pub const fn new(datetime: ASN1DateTime) -> Self { + GeneralizedTime(datetime) + } + + pub fn from_bytes(bytes: &[u8]) -> Result<Self> { + // X.680 section 42 defines a GeneralizedTime as a VisibleString restricted to: + // + // a) a string representing the calendar date, as specified in ISO 8601, with a four-digit representation of the + // year, a two-digit representation of the month and a two-digit representation of the day, without use of + // separators, followed by a string representing the time of day, as specified in ISO 8601, without separators + // other than decimal comma or decimal period (as provided for in ISO 8601), and with no terminating Z (as + // provided for in ISO 8601); or + // b) the characters in a) above followed by an upper-case letter Z ; or + // c) he characters in a) above followed by a string representing a local time differential, as specified in + // ISO 8601, without separators. + let (year, month, day, hour, minute, rem) = match bytes { + [year1, year2, year3, year4, mon1, mon2, day1, day2, hour1, hour2, min1, min2, rem @ ..] => + { + let year_hi = decode_decimal(Self::TAG, *year1, *year2)?; + let year_lo = decode_decimal(Self::TAG, *year3, *year4)?; + let year = (year_hi as u32) * 100 + (year_lo as u32); + let month = decode_decimal(Self::TAG, *mon1, *mon2)?; + let day = decode_decimal(Self::TAG, *day1, *day2)?; + let hour = decode_decimal(Self::TAG, *hour1, *hour2)?; + let minute = decode_decimal(Self::TAG, *min1, *min2)?; + (year, month, day, hour, minute, rem) + } + _ => return Err(Self::TAG.invalid_value("malformed time string (not yymmddhhmm)")), + }; + if rem.is_empty() { + return Err(Self::TAG.invalid_value("malformed time string")); + } + // check for seconds + let (second, rem) = match rem { + [sec1, sec2, rem @ ..] => { + let second = decode_decimal(Self::TAG, *sec1, *sec2)?; + (second, rem) + } + _ => (0, rem), + }; + if month > 12 || day > 31 || hour > 23 || minute > 59 || second > 59 { + // eprintln!("GeneralizedTime: time checks failed"); + // eprintln!(" month:{}", month); + // eprintln!(" day:{}", day); + // eprintln!(" hour:{}", hour); + // eprintln!(" minute:{}", minute); + // eprintln!(" second:{}", second); + return Err(Self::TAG.invalid_value("time components with invalid values")); + } + if rem.is_empty() { + // case a): no fractional seconds part, and no terminating Z + return Ok(GeneralizedTime(ASN1DateTime::new( + year, + month, + day, + hour, + minute, + second, + None, + ASN1TimeZone::Undefined, + ))); + } + // check for fractional seconds + let (millisecond, rem) = match rem { + [b'.' | b',', rem @ ..] => { + let mut fsecond = 0; + let mut rem = rem; + let mut digits = 0; + for idx in 0..=4 { + if rem.is_empty() { + if idx == 0 { + // dot or comma, but no following digit + return Err(Self::TAG.invalid_value( + "malformed time string (dot or comma but no digits)", + )); + } + digits = idx; + break; + } + if idx == 4 { + return Err( + Self::TAG.invalid_value("malformed time string (invalid milliseconds)") + ); + } + match rem[0] { + b'0'..=b'9' => { + // cannot overflow, max 4 digits will be read + fsecond = fsecond * 10 + (rem[0] - b'0') as u16; + } + b'Z' | b'+' | b'-' => { + digits = idx; + break; + } + _ => { + return Err(Self::TAG.invalid_value( + "malformed time string (invalid milliseconds/timezone)", + )) + } + } + rem = &rem[1..]; + } + // fix fractional seconds depending on the number of digits + // for ex, date "xxxx.3" means 3000 milliseconds, not 3 + let fsecond = match digits { + 1 => fsecond * 100, + 2 => fsecond * 10, + _ => fsecond, + }; + (Some(fsecond), rem) + } + _ => (None, rem), + }; + // check timezone + if rem.is_empty() { + // case a): fractional seconds part, and no terminating Z + return Ok(GeneralizedTime(ASN1DateTime::new( + year, + month, + day, + hour, + minute, + second, + millisecond, + ASN1TimeZone::Undefined, + ))); + } + let tz = match rem { + [b'Z'] => ASN1TimeZone::Z, + [b'+', h1, h2, m1, m2] => { + let hh = decode_decimal(Self::TAG, *h1, *h2)?; + let mm = decode_decimal(Self::TAG, *m1, *m2)?; + ASN1TimeZone::Offset(hh as i8, mm as i8) + } + [b'-', h1, h2, m1, m2] => { + let hh = decode_decimal(Self::TAG, *h1, *h2)?; + let mm = decode_decimal(Self::TAG, *m1, *m2)?; + ASN1TimeZone::Offset(-(hh as i8), mm as i8) + } + _ => return Err(Self::TAG.invalid_value("malformed time string: no time zone")), + }; + Ok(GeneralizedTime(ASN1DateTime::new( + year, + month, + day, + hour, + minute, + second, + millisecond, + tz, + ))) + } + + /// Return a ISO 8601 combined date and time with time zone. + #[cfg(feature = "datetime")] + #[cfg_attr(docsrs, doc(cfg(feature = "datetime")))] + pub fn utc_datetime(&self) -> Result<OffsetDateTime> { + self.0.to_datetime() + } +} + +impl<'a> TryFrom<Any<'a>> for GeneralizedTime { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<GeneralizedTime> { + TryFrom::try_from(&any) + } +} + +impl<'a, 'b> TryFrom<&'b Any<'a>> for GeneralizedTime { + type Error = Error; + + fn try_from(any: &'b Any<'a>) -> Result<GeneralizedTime> { + any.tag().assert_eq(Self::TAG)?; + #[allow(clippy::trivially_copy_pass_by_ref)] + fn is_visible(b: &u8) -> bool { + 0x20 <= *b && *b <= 0x7f + } + if !any.data.iter().all(is_visible) { + return Err(Error::StringInvalidCharset); + } + + GeneralizedTime::from_bytes(any.data) + } +} + +impl fmt::Display for GeneralizedTime { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let dt = &self.0; + let fsec = match self.0.millisecond { + Some(v) => format!(".{}", v), + None => String::new(), + }; + match dt.tz { + ASN1TimeZone::Undefined => write!( + f, + "{:04}-{:02}-{:02} {:02}:{:02}:{:02}{}", + dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, fsec + ), + ASN1TimeZone::Z => write!( + f, + "{:04}-{:02}-{:02} {:02}:{:02}:{:02}{}Z", + dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, fsec + ), + ASN1TimeZone::Offset(hh, mm) => { + let (s, hh) = if hh > 0 { ('+', hh) } else { ('-', -hh) }; + write!( + f, + "{:04}-{:02}-{:02} {:02}:{:02}:{:02}{}{}{:02}{:02}", + dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, fsec, s, hh, mm + ) + } + } + } +} + +impl CheckDerConstraints for GeneralizedTime { + fn check_constraints(any: &Any) -> Result<()> { + // X.690 section 11.7.1: The encoding shall terminate with a "Z" + if any.data.last() != Some(&b'Z') { + return Err(Error::DerConstraintFailed(DerConstraint::MissingTimeZone)); + } + // X.690 section 11.7.2: The seconds element shall always be present. + // XXX + // X.690 section 11.7.4: The decimal point element, if present, shall be the point option "." + if any.data.iter().any(|&b| b == b',') { + return Err(Error::DerConstraintFailed(DerConstraint::MissingSeconds)); + } + Ok(()) + } +} + +impl DerAutoDerive for GeneralizedTime {} + +impl Tagged for GeneralizedTime { + const TAG: Tag = Tag::GeneralizedTime; +} + +#[cfg(feature = "std")] +impl ToDer for GeneralizedTime { + fn to_der_len(&self) -> Result<usize> { + // data: + // - 8 bytes for YYYYMMDD + // - 6 for hhmmss in DER (X.690 section 11.7.2) + // - (variable) the fractional part, without trailing zeros, with a point "." + // - 1 for the character Z in DER (X.690 section 11.7.1) + // data length: 15 + fractional part + // + // thus, length will always be on 1 byte (short length) and + // class+structure+tag also on 1 + // + // total: = 1 (class+constructed+tag) + 1 (length) + 15 + fractional + let num_digits = match self.0.millisecond { + None => 0, + Some(v) => 1 + v.to_string().len(), + }; + Ok(2 + 15 + num_digits) + } + + fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + // see above for length value + let num_digits = match self.0.millisecond { + None => 0, + Some(v) => 1 + v.to_string().len() as u8, + }; + writer + .write(&[Self::TAG.0 as u8, 15 + num_digits]) + .map_err(Into::into) + } + + fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let fractional = match self.0.millisecond { + None => "".to_string(), + Some(v) => format!(".{}", v), + }; + let num_digits = fractional.len(); + write!( + writer, + "{:04}{:02}{:02}{:02}{:02}{:02}{}Z", + self.0.year, + self.0.month, + self.0.day, + self.0.hour, + self.0.minute, + self.0.second, + fractional, + )?; + // write_fmt returns (), see above for length value + Ok(15 + num_digits) + } +} diff --git a/src/asn1_types/integer.rs b/src/asn1_types/integer.rs new file mode 100644 index 0000000..59f846a --- /dev/null +++ b/src/asn1_types/integer.rs @@ -0,0 +1,712 @@ +use crate::*; +use alloc::borrow::Cow; +use alloc::vec; +use core::convert::{TryFrom, TryInto}; + +#[cfg(feature = "bigint")] +#[cfg_attr(docsrs, doc(cfg(feature = "bigint")))] +pub use num_bigint::{BigInt, BigUint, Sign}; + +/// Decode an unsigned integer into a big endian byte slice with all leading +/// zeroes removed (if positive) and extra 0xff remove (if negative) +fn trim_slice<'a>(any: &'a Any<'_>) -> Result<&'a [u8]> { + let bytes = any.data; + + if bytes.is_empty() || (bytes[0] != 0x00 && bytes[0] != 0xff) { + return Ok(bytes); + } + + match bytes.iter().position(|&b| b != 0) { + // first byte is not 0 + Some(0) => (), + // all bytes are 0 + None => return Ok(&bytes[bytes.len() - 1..]), + Some(first) => return Ok(&bytes[first..]), + } + + // same for negative integers : skip byte 0->n if byte 0->n = 0xff AND byte n+1 >= 0x80 + match bytes.windows(2).position(|s| match s { + &[a, b] => !(a == 0xff && b >= 0x80), + _ => true, + }) { + // first byte is not 0xff + Some(0) => (), + // all bytes are 0xff + None => return Ok(&bytes[bytes.len() - 1..]), + Some(first) => return Ok(&bytes[first..]), + } + + Ok(bytes) +} + +/// Decode an unsigned integer into a byte array of the requested size +/// containing a big endian integer. +fn decode_array_uint<const N: usize>(any: &Any<'_>) -> Result<[u8; N]> { + if is_highest_bit_set(any.data) { + return Err(Error::IntegerNegative); + } + let input = trim_slice(any)?; + + if input.len() > N { + return Err(Error::IntegerTooLarge); + } + + // Input has leading zeroes removed, so we need to add them back + let mut output = [0u8; N]; + assert!(input.len() <= N); + output[N.saturating_sub(input.len())..].copy_from_slice(input); + Ok(output) +} + +/// Decode an unsigned integer of the specified size. +/// +/// Returns a byte array of the requested size containing a big endian integer. +fn decode_array_int<const N: usize>(any: &Any<'_>) -> Result<[u8; N]> { + if any.data.len() > N { + return Err(Error::IntegerTooLarge); + } + + // any.tag().assert_eq(Tag::Integer)?; + let mut output = [0xFFu8; N]; + let offset = N.saturating_sub(any.as_bytes().len()); + output[offset..].copy_from_slice(any.as_bytes()); + Ok(output) +} + +/// Is the highest bit of the first byte in the slice 1? (if present) +#[inline] +fn is_highest_bit_set(bytes: &[u8]) -> bool { + bytes + .first() + .map(|byte| byte & 0b10000000 != 0) + .unwrap_or(false) +} + +macro_rules! impl_int { + ($uint:ty => $int:ty) => { + impl<'a> TryFrom<Any<'a>> for $int { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<Self> { + TryFrom::try_from(&any) + } + } + + impl<'a, 'b> TryFrom<&'b Any<'a>> for $int { + type Error = Error; + + fn try_from(any: &'b Any<'a>) -> Result<Self> { + any.tag().assert_eq(Self::TAG)?; + any.header.assert_primitive()?; + let uint = if is_highest_bit_set(any.as_bytes()) { + <$uint>::from_be_bytes(decode_array_int(&any)?) + } else { + // read as uint, but check if the value will fit in a signed integer + let u = <$uint>::from_be_bytes(decode_array_uint(&any)?); + if u > <$int>::MAX as $uint { + return Err(Error::IntegerTooLarge); + } + u + }; + Ok(uint as $int) + } + } + + impl CheckDerConstraints for $int { + fn check_constraints(any: &Any) -> Result<()> { + check_der_int_constraints(any) + } + } + + impl DerAutoDerive for $int {} + + impl Tagged for $int { + const TAG: Tag = Tag::Integer; + } + + #[cfg(feature = "std")] + impl ToDer for $int { + fn to_der_len(&self) -> Result<usize> { + let int = Integer::from(*self); + int.to_der_len() + } + + fn write_der(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let int = Integer::from(*self); + int.write_der(writer) + } + + fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let int = Integer::from(*self); + int.write_der_header(writer) + } + + fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let int = Integer::from(*self); + int.write_der_content(writer) + } + } + }; +} + +macro_rules! impl_uint { + ($ty:ty) => { + impl<'a> TryFrom<Any<'a>> for $ty { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<Self> { + TryFrom::try_from(&any) + } + } + impl<'a, 'b> TryFrom<&'b Any<'a>> for $ty { + type Error = Error; + + fn try_from(any: &'b Any<'a>) -> Result<Self> { + any.tag().assert_eq(Self::TAG)?; + any.header.assert_primitive()?; + let result = Self::from_be_bytes(decode_array_uint(any)?); + Ok(result) + } + } + impl CheckDerConstraints for $ty { + fn check_constraints(any: &Any) -> Result<()> { + check_der_int_constraints(any) + } + } + + impl DerAutoDerive for $ty {} + + impl Tagged for $ty { + const TAG: Tag = Tag::Integer; + } + + #[cfg(feature = "std")] + impl ToDer for $ty { + fn to_der_len(&self) -> Result<usize> { + let int = Integer::from(*self); + int.to_der_len() + } + + fn write_der(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let int = Integer::from(*self); + int.write_der(writer) + } + + fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let int = Integer::from(*self); + int.write_der_header(writer) + } + + fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let int = Integer::from(*self); + int.write_der_content(writer) + } + } + }; +} + +impl_uint!(u8); +impl_uint!(u16); +impl_uint!(u32); +impl_uint!(u64); +impl_uint!(u128); +impl_int!(u8 => i8); +impl_int!(u16 => i16); +impl_int!(u32 => i32); +impl_int!(u64 => i64); +impl_int!(u128 => i128); + +/// ASN.1 `INTEGER` type +/// +/// Generic representation for integer types. +/// BER/DER integers can be of any size, so it is not possible to store them as simple integers (they +/// are stored as raw bytes). +/// +/// The internal representation can be obtained using `.as_ref()`. +/// +/// # Note +/// +/// Methods from/to BER and DER encodings are also implemented for primitive types +/// (`u8`, `u16` to `u128`, and `i8` to `i128`). +/// In most cases, it is easier to use these types directly. +/// +/// # Examples +/// +/// Creating an `Integer` +/// +/// ``` +/// use asn1_rs::Integer; +/// +/// // unsigned +/// let i = Integer::from(4); +/// assert_eq!(i.as_ref(), &[4]); +/// // signed +/// let j = Integer::from(-2); +/// assert_eq!(j.as_ref(), &[0xfe]); +/// ``` +/// +/// Converting an `Integer` to a primitive type (using the `TryInto` trait) +/// +/// ``` +/// use asn1_rs::{Error, Integer}; +/// use std::convert::TryInto; +/// +/// let i = Integer::new(&[0x12, 0x34, 0x56, 0x78]); +/// // converts to an u32 +/// let n: u32 = i.try_into().unwrap(); +/// +/// // Same, but converting to an u16: will fail, value cannot fit into an u16 +/// let i = Integer::new(&[0x12, 0x34, 0x56, 0x78]); +/// assert_eq!(i.try_into() as Result<u16, _>, Err(Error::IntegerTooLarge)); +/// ``` +/// +/// Encoding an `Integer` to DER +/// +/// ``` +/// use asn1_rs::{Integer, ToDer}; +/// +/// let i = Integer::from(4); +/// let v = i.to_der_vec().unwrap(); +/// assert_eq!(&v, &[2, 1, 4]); +/// +/// // same, with primitive types +/// let v = 4.to_der_vec().unwrap(); +/// assert_eq!(&v, &[2, 1, 4]); +/// ``` +#[derive(Debug, Eq, PartialEq)] +pub struct Integer<'a> { + pub(crate) data: Cow<'a, [u8]>, +} + +impl<'a> Integer<'a> { + /// Creates a new `Integer` containing the given value (borrowed). + #[inline] + pub const fn new(s: &'a [u8]) -> Self { + Integer { + data: Cow::Borrowed(s), + } + } + + /// Creates a borrowed `Any` for this object + #[inline] + pub fn any(&'a self) -> Any<'a> { + Any::from_tag_and_data(Self::TAG, &self.data) + } + + /// Returns a `BigInt` built from this `Integer` value. + #[cfg(feature = "bigint")] + #[cfg_attr(docsrs, doc(cfg(feature = "bigint")))] + pub fn as_bigint(&self) -> BigInt { + BigInt::from_signed_bytes_be(&self.data) + } + + /// Returns a `BigUint` built from this `Integer` value. + #[cfg(feature = "bigint")] + #[cfg_attr(docsrs, doc(cfg(feature = "bigint")))] + pub fn as_biguint(&self) -> Result<BigUint> { + if is_highest_bit_set(&self.data) { + Err(Error::IntegerNegative) + } else { + Ok(BigUint::from_bytes_be(&self.data)) + } + } + + /// Build an `Integer` from a constant array of bytes representation of an integer. + pub fn from_const_array<const N: usize>(b: [u8; N]) -> Self { + // if high bit set -> add leading 0 to ensure unsigned + if is_highest_bit_set(&b) { + let mut bytes = vec![0]; + bytes.extend_from_slice(&b); + + Integer { + data: Cow::Owned(bytes), + } + } + // otherwise -> remove 0 unless next has high bit set + else { + let mut idx = 0; + + while idx < b.len() - 1 { + if b[idx] == 0 && b[idx + 1] < 0x80 { + idx += 1; + continue; + } + break; + } + + Integer { + data: Cow::Owned(b[idx..].to_vec()), + } + } + } + + fn from_const_array_negative<const N: usize>(b: [u8; N]) -> Self { + let mut idx = 0; + + // Skip leading FF unless next has high bit clear + while idx < b.len() - 1 { + if b[idx] == 0xFF && b[idx + 1] >= 0x80 { + idx += 1; + continue; + } + break; + } + + if idx == b.len() { + Integer { + data: Cow::Borrowed(&[0]), + } + } else { + Integer { + data: Cow::Owned(b[idx..].to_vec()), + } + } + } +} + +macro_rules! impl_from_to { + ($ty:ty, $sty:expr, $from:ident, $to:ident) => { + impl From<$ty> for Integer<'_> { + fn from(i: $ty) -> Self { + Self::$from(i) + } + } + + impl TryFrom<Integer<'_>> for $ty { + type Error = Error; + + fn try_from(value: Integer<'_>) -> Result<Self> { + value.$to() + } + } + + impl Integer<'_> { + #[doc = "Attempts to convert an `Integer` to a `"] + #[doc = $sty] + #[doc = "`."] + #[doc = ""] + #[doc = "This function returns an `IntegerTooLarge` error if the integer will not fit into the output type."] + pub fn $to(&self) -> Result<$ty> { + self.any().try_into() + } + } + }; + (IMPL SIGNED $ty:ty, $sty:expr, $from:ident, $to:ident) => { + impl_from_to!($ty, $sty, $from, $to); + + impl Integer<'_> { + #[doc = "Converts a `"] + #[doc = $sty] + #[doc = "` to an `Integer`"] + #[doc = ""] + #[doc = "Note: this function allocates data."] + pub fn $from(i: $ty) -> Self { + let b = i.to_be_bytes(); + if i >= 0 { + Self::from_const_array(b) + } else { + Self::from_const_array_negative(b) + } + } + } + }; + (IMPL UNSIGNED $ty:ty, $sty:expr, $from:ident, $to:ident) => { + impl_from_to!($ty, $sty, $from, $to); + + impl Integer<'_> { + #[doc = "Converts a `"] + #[doc = $sty] + #[doc = "` to an `Integer`"] + #[doc = ""] + #[doc = "Note: this function allocates data."] + pub fn $from(i: $ty) -> Self { + Self::from_const_array(i.to_be_bytes()) + } + } + }; + (SIGNED $ty:ty, $from:ident, $to:ident) => { + impl_from_to!(IMPL SIGNED $ty, stringify!($ty), $from, $to); + }; + (UNSIGNED $ty:ty, $from:ident, $to:ident) => { + impl_from_to!(IMPL UNSIGNED $ty, stringify!($ty), $from, $to); + }; +} + +impl_from_to!(SIGNED i8, from_i8, as_i8); +impl_from_to!(SIGNED i16, from_i16, as_i16); +impl_from_to!(SIGNED i32, from_i32, as_i32); +impl_from_to!(SIGNED i64, from_i64, as_i64); +impl_from_to!(SIGNED i128, from_i128, as_i128); + +impl_from_to!(UNSIGNED u8, from_u8, as_u8); +impl_from_to!(UNSIGNED u16, from_u16, as_u16); +impl_from_to!(UNSIGNED u32, from_u32, as_u32); +impl_from_to!(UNSIGNED u64, from_u64, as_u64); +impl_from_to!(UNSIGNED u128, from_u128, as_u128); + +impl<'a> AsRef<[u8]> for Integer<'a> { + fn as_ref(&self) -> &[u8] { + &self.data + } +} + +impl<'a> TryFrom<Any<'a>> for Integer<'a> { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<Integer<'a>> { + TryFrom::try_from(&any) + } +} + +impl<'a, 'b> TryFrom<&'b Any<'a>> for Integer<'a> { + type Error = Error; + + fn try_from(any: &'b Any<'a>) -> Result<Integer<'a>> { + any.tag().assert_eq(Self::TAG)?; + Ok(Integer { + data: Cow::Borrowed(any.data), + }) + } +} + +impl<'a> CheckDerConstraints for Integer<'a> { + fn check_constraints(any: &Any) -> Result<()> { + check_der_int_constraints(any) + } +} + +fn check_der_int_constraints(any: &Any) -> Result<()> { + any.header.assert_primitive()?; + any.header.length.assert_definite()?; + match any.as_bytes() { + [] => Err(Error::DerConstraintFailed(DerConstraint::IntegerEmpty)), + [0] => Ok(()), + // leading zeroes + [0, byte, ..] if *byte < 0x80 => Err(Error::DerConstraintFailed( + DerConstraint::IntegerLeadingZeroes, + )), + // negative integer with non-minimal encoding + [0xff, byte, ..] if *byte >= 0x80 => { + Err(Error::DerConstraintFailed(DerConstraint::IntegerLeadingFF)) + } + _ => Ok(()), + } +} + +impl DerAutoDerive for Integer<'_> {} + +impl<'a> Tagged for Integer<'a> { + const TAG: Tag = Tag::Integer; +} + +#[cfg(feature = "std")] +impl ToDer for Integer<'_> { + fn to_der_len(&self) -> Result<usize> { + let sz = self.data.len(); + if sz < 127 { + // 1 (class+tag) + 1 (length) + len + Ok(2 + sz) + } else { + // hmm, a very long integer. anyway: + // 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.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.data).map_err(Into::into) + } +} + +/// Helper macro to declare integers at compile-time +/// +/// [`Integer`] stores the encoded representation of the integer, so declaring +/// an integer requires to either use a runtime function or provide the encoded value. +/// This macro simplifies this task by encoding the value. +/// It can be used the following ways: +/// +/// - `int!(1234)`: Create a const expression for the corresponding `Integer<'static>` +/// - `int!(raw 1234)`: Return the DER encoded form as a byte array (hex-encoded, big-endian +/// representation from the integer, with leading zeroes removed). +/// +/// # Examples +/// +/// ```rust +/// use asn1_rs::{int, Integer}; +/// +/// const INT0: Integer = int!(1234); +/// ``` +#[macro_export] +macro_rules! int { + (raw $item:expr) => { + $crate::exports::asn1_rs_impl::encode_int!($item) + }; + (rel $item:expr) => { + $crate::exports::asn1_rs_impl::encode_int!(rel $item) + }; + ($item:expr) => { + $crate::Integer::new( + &$crate::int!(raw $item), + ) + }; +} + +#[cfg(test)] +mod tests { + use crate::{Any, FromDer, Header, Tag, ToDer}; + use std::convert::TryInto; + + // Vectors from Section 5.7 of: + // https://luca.ntop.org/Teaching/Appunti/asn1.html + pub(crate) const I0_BYTES: &[u8] = &[0x02, 0x01, 0x00]; + pub(crate) const I127_BYTES: &[u8] = &[0x02, 0x01, 0x7F]; + pub(crate) const I128_BYTES: &[u8] = &[0x02, 0x02, 0x00, 0x80]; + pub(crate) const I256_BYTES: &[u8] = &[0x02, 0x02, 0x01, 0x00]; + pub(crate) const INEG128_BYTES: &[u8] = &[0x02, 0x01, 0x80]; + pub(crate) const INEG129_BYTES: &[u8] = &[0x02, 0x02, 0xFF, 0x7F]; + + // Additional vectors + pub(crate) const I255_BYTES: &[u8] = &[0x02, 0x02, 0x00, 0xFF]; + pub(crate) const I32767_BYTES: &[u8] = &[0x02, 0x02, 0x7F, 0xFF]; + pub(crate) const I65535_BYTES: &[u8] = &[0x02, 0x03, 0x00, 0xFF, 0xFF]; + pub(crate) const INEG32768_BYTES: &[u8] = &[0x02, 0x02, 0x80, 0x00]; + + #[test] + fn decode_i8() { + assert_eq!(0, i8::from_der(I0_BYTES).unwrap().1); + assert_eq!(127, i8::from_der(I127_BYTES).unwrap().1); + assert_eq!(-128, i8::from_der(INEG128_BYTES).unwrap().1); + } + + #[test] + fn encode_i8() { + assert_eq!(0i8.to_der_vec().unwrap(), I0_BYTES); + assert_eq!(127i8.to_der_vec().unwrap(), I127_BYTES); + assert_eq!((-128i8).to_der_vec().unwrap(), INEG128_BYTES); + } + + #[test] + fn decode_i16() { + assert_eq!(0, i16::from_der(I0_BYTES).unwrap().1); + assert_eq!(127, i16::from_der(I127_BYTES).unwrap().1); + assert_eq!(128, i16::from_der(I128_BYTES).unwrap().1); + assert_eq!(255, i16::from_der(I255_BYTES).unwrap().1); + assert_eq!(256, i16::from_der(I256_BYTES).unwrap().1); + assert_eq!(32767, i16::from_der(I32767_BYTES).unwrap().1); + assert_eq!(-128, i16::from_der(INEG128_BYTES).unwrap().1); + assert_eq!(-129, i16::from_der(INEG129_BYTES).unwrap().1); + assert_eq!(-32768, i16::from_der(INEG32768_BYTES).unwrap().1); + } + + #[test] + fn encode_i16() { + assert_eq!(0i16.to_der_vec().unwrap(), I0_BYTES); + assert_eq!(127i16.to_der_vec().unwrap(), I127_BYTES); + assert_eq!(128i16.to_der_vec().unwrap(), I128_BYTES); + assert_eq!(255i16.to_der_vec().unwrap(), I255_BYTES); + assert_eq!(256i16.to_der_vec().unwrap(), I256_BYTES); + assert_eq!(32767i16.to_der_vec().unwrap(), I32767_BYTES); + assert_eq!((-128i16).to_der_vec().unwrap(), INEG128_BYTES); + assert_eq!((-129i16).to_der_vec().unwrap(), INEG129_BYTES); + assert_eq!((-32768i16).to_der_vec().unwrap(), INEG32768_BYTES); + } + + #[test] + fn decode_u8() { + assert_eq!(0, u8::from_der(I0_BYTES).unwrap().1); + assert_eq!(127, u8::from_der(I127_BYTES).unwrap().1); + assert_eq!(255, u8::from_der(I255_BYTES).unwrap().1); + } + + #[test] + fn encode_u8() { + assert_eq!(0u8.to_der_vec().unwrap(), I0_BYTES); + assert_eq!(127u8.to_der_vec().unwrap(), I127_BYTES); + assert_eq!(255u8.to_der_vec().unwrap(), I255_BYTES); + } + + #[test] + fn decode_u16() { + assert_eq!(0, u16::from_der(I0_BYTES).unwrap().1); + assert_eq!(127, u16::from_der(I127_BYTES).unwrap().1); + assert_eq!(255, u16::from_der(I255_BYTES).unwrap().1); + assert_eq!(256, u16::from_der(I256_BYTES).unwrap().1); + assert_eq!(32767, u16::from_der(I32767_BYTES).unwrap().1); + assert_eq!(65535, u16::from_der(I65535_BYTES).unwrap().1); + } + + #[test] + fn encode_u16() { + assert_eq!(0u16.to_der_vec().unwrap(), I0_BYTES); + assert_eq!(127u16.to_der_vec().unwrap(), I127_BYTES); + assert_eq!(255u16.to_der_vec().unwrap(), I255_BYTES); + assert_eq!(256u16.to_der_vec().unwrap(), I256_BYTES); + assert_eq!(32767u16.to_der_vec().unwrap(), I32767_BYTES); + assert_eq!(65535u16.to_der_vec().unwrap(), I65535_BYTES); + } + + /// Integers must be encoded with a minimum number of octets + #[test] + fn reject_non_canonical() { + assert!(i8::from_der(&[0x02, 0x02, 0x00, 0x00]).is_err()); + assert!(i16::from_der(&[0x02, 0x02, 0x00, 0x00]).is_err()); + assert!(u8::from_der(&[0x02, 0x02, 0x00, 0x00]).is_err()); + assert!(u16::from_der(&[0x02, 0x02, 0x00, 0x00]).is_err()); + } + + #[test] + fn declare_int() { + let int = super::int!(1234); + assert_eq!(int.try_into(), Ok(1234)); + } + + #[test] + fn trim_slice() { + use super::trim_slice; + let h = Header::new_simple(Tag(0)); + // no zero nor ff - nothing to remove + let input: &[u8] = &[0x7f, 0xff, 0x00, 0x02]; + assert_eq!(Ok(input), trim_slice(&Any::new(h.clone(), input))); + // + // 0x00 + // + // empty - nothing to remove + let input: &[u8] = &[]; + assert_eq!(Ok(input), trim_slice(&Any::new(h.clone(), input))); + // one zero - nothing to remove + let input: &[u8] = &[0]; + assert_eq!(Ok(input), trim_slice(&Any::new(h.clone(), input))); + // all zeroes - keep only one + let input: &[u8] = &[0, 0, 0]; + assert_eq!(Ok(&input[2..]), trim_slice(&Any::new(h.clone(), input))); + // some zeroes - keep only the non-zero part + let input: &[u8] = &[0, 0, 1]; + assert_eq!(Ok(&input[2..]), trim_slice(&Any::new(h.clone(), input))); + // + // 0xff + // + // one ff - nothing to remove + let input: &[u8] = &[0xff]; + assert_eq!(Ok(input), trim_slice(&Any::new(h.clone(), input))); + // all ff - keep only one + let input: &[u8] = &[0xff, 0xff, 0xff]; + assert_eq!(Ok(&input[2..]), trim_slice(&Any::new(h.clone(), input))); + // some ff - keep only the non-zero part + let input: &[u8] = &[0xff, 0xff, 1]; + assert_eq!(Ok(&input[1..]), trim_slice(&Any::new(h.clone(), input))); + // some ff and a MSB 1 - keep only the non-zero part + let input: &[u8] = &[0xff, 0xff, 0x80, 1]; + assert_eq!(Ok(&input[2..]), trim_slice(&Any::new(h.clone(), input))); + } +} diff --git a/src/asn1_types/mod.rs b/src/asn1_types/mod.rs new file mode 100644 index 0000000..33e50d6 --- /dev/null +++ b/src/asn1_types/mod.rs @@ -0,0 +1,26 @@ +mod any; +mod bitstring; +mod boolean; +mod choice; +mod embedded_pdv; +mod end_of_content; +mod enumerated; +mod generalizedtime; +mod integer; +mod null; +mod object_descriptor; +mod octetstring; +mod oid; +mod optional; +mod real; +mod sequence; +mod set; +mod strings; +mod tagged; +mod utctime; + +pub use { + any::*, bitstring::*, boolean::*, choice::*, embedded_pdv::*, end_of_content::*, enumerated::*, + generalizedtime::*, integer::*, null::*, object_descriptor::*, octetstring::*, oid::*, + optional::*, real::*, sequence::*, set::*, strings::*, tagged::*, utctime::*, +}; diff --git a/src/asn1_types/null.rs b/src/asn1_types/null.rs new file mode 100644 index 0000000..ba9f0f5 --- /dev/null +++ b/src/asn1_types/null.rs @@ -0,0 +1,99 @@ +use crate::*; +use core::convert::TryFrom; + +/// ASN.1 `NULL` type +#[derive(Debug, PartialEq, Eq)] +pub struct Null {} + +impl Null { + pub const fn new() -> Self { + Null {} + } +} + +impl<'a> TryFrom<Any<'a>> for Null { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<Null> { + TryFrom::try_from(&any) + } +} + +impl<'a, 'b> TryFrom<&'b Any<'a>> for Null { + type Error = Error; + + fn try_from(any: &'b Any<'a>) -> Result<Null> { + any.tag().assert_eq(Self::TAG)?; + if !any.header.length.is_null() { + return Err(Error::InvalidLength); + } + Ok(Null {}) + } +} + +impl CheckDerConstraints for Null { + fn check_constraints(_any: &Any) -> Result<()> { + Ok(()) + } +} + +impl DerAutoDerive for Null {} + +impl Tagged for Null { + const TAG: Tag = Tag::Null; +} + +#[cfg(feature = "std")] +impl ToDer for Null { + fn to_der_len(&self) -> Result<usize> { + Ok(2) + } + + fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + writer.write(&[0x05, 0x00]).map_err(Into::into) + } + + fn write_der_content(&self, _writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + Ok(0) + } +} + +impl<'a> TryFrom<Any<'a>> for () { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<()> { + any.tag().assert_eq(Self::TAG)?; + any.header.assert_primitive()?; + if !any.header.length.is_null() { + return Err(Error::InvalidLength); + } + Ok(()) + } +} + +impl CheckDerConstraints for () { + fn check_constraints(_any: &Any) -> Result<()> { + Ok(()) + } +} + +impl DerAutoDerive for () {} + +impl Tagged for () { + const TAG: Tag = Tag::Null; +} + +#[cfg(feature = "std")] +impl ToDer for () { + fn to_der_len(&self) -> Result<usize> { + Ok(2) + } + + fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + writer.write(&[0x05, 0x00]).map_err(Into::into) + } + + fn write_der_content(&self, _writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + Ok(0) + } +} diff --git a/src/asn1_types/object_descriptor.rs b/src/asn1_types/object_descriptor.rs new file mode 100644 index 0000000..db78870 --- /dev/null +++ b/src/asn1_types/object_descriptor.rs @@ -0,0 +1,17 @@ +use crate::{asn1_string, TestValidCharset}; +use crate::{Error, Result}; +use alloc::string::String; + +// X.680 section 44.3 +// ObjectDescriptor ::= [UNIVERSAL 7] IMPLICIT GraphicString + +asn1_string!(ObjectDescriptor); + +impl<'a> TestValidCharset for ObjectDescriptor<'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/octetstring.rs b/src/asn1_types/octetstring.rs new file mode 100644 index 0000000..b4b71e5 --- /dev/null +++ b/src/asn1_types/octetstring.rs @@ -0,0 +1,157 @@ +use crate::*; +use alloc::borrow::Cow; +use core::convert::TryFrom; + +/// ASN.1 `OCTETSTRING` type +#[derive(Debug, PartialEq, Eq)] +pub struct OctetString<'a> { + data: Cow<'a, [u8]>, +} + +impl<'a> OctetString<'a> { + pub const fn new(s: &'a [u8]) -> Self { + OctetString { + data: Cow::Borrowed(s), + } + } + + /// Get the bytes representation of the *content* + pub fn as_cow(&'a self) -> &Cow<'a, [u8]> { + &self.data + } + + /// Get the bytes representation of the *content* + pub fn into_cow(self) -> Cow<'a, [u8]> { + self.data + } +} + +impl<'a> AsRef<[u8]> for OctetString<'a> { + fn as_ref(&self) -> &[u8] { + &self.data + } +} + +impl<'a> From<&'a [u8]> for OctetString<'a> { + fn from(b: &'a [u8]) -> Self { + OctetString { + data: Cow::Borrowed(b), + } + } +} + +impl<'a> TryFrom<Any<'a>> for OctetString<'a> { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<OctetString<'a>> { + TryFrom::try_from(&any) + } +} + +impl<'a, 'b> TryFrom<&'b Any<'a>> for OctetString<'a> { + type Error = Error; + + fn try_from(any: &'b Any<'a>) -> Result<OctetString<'a>> { + any.tag().assert_eq(Self::TAG)?; + Ok(OctetString { + data: Cow::Borrowed(any.data), + }) + } +} + +impl<'a> CheckDerConstraints for OctetString<'a> { + fn check_constraints(any: &Any) -> Result<()> { + // X.690 section 10.2 + any.header.assert_primitive()?; + Ok(()) + } +} + +impl DerAutoDerive for OctetString<'_> {} + +impl<'a> Tagged for OctetString<'a> { + const TAG: Tag = Tag::OctetString; +} + +#[cfg(feature = "std")] +impl ToDer for OctetString<'_> { + fn to_der_len(&self) -> Result<usize> { + let sz = self.data.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.data.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.data).map_err(Into::into) + } +} + +impl<'a> TryFrom<Any<'a>> for &'a [u8] { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<&'a [u8]> { + any.tag().assert_eq(Self::TAG)?; + let s = OctetString::try_from(any)?; + match s.data { + Cow::Borrowed(s) => Ok(s), + Cow::Owned(_) => Err(Error::LifetimeError), + } + } +} + +impl<'a> CheckDerConstraints for &'a [u8] { + fn check_constraints(any: &Any) -> Result<()> { + // X.690 section 10.2 + any.header.assert_primitive()?; + Ok(()) + } +} + +impl DerAutoDerive for &'_ [u8] {} + +impl<'a> Tagged for &'a [u8] { + const TAG: Tag = Tag::OctetString; +} + +#[cfg(feature = "std")] +impl ToDer for &'_ [u8] { + fn to_der_len(&self) -> Result<usize> { + let header = Header::new( + Class::Universal, + false, + Self::TAG, + Length::Definite(self.len()), + ); + Ok(header.to_der_len()? + self.len()) + } + + 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).map_err(Into::into) + } +} diff --git a/src/asn1_types/oid.rs b/src/asn1_types/oid.rs new file mode 100644 index 0000000..a6629b4 --- /dev/null +++ b/src/asn1_types/oid.rs @@ -0,0 +1,517 @@ +use crate::*; +use alloc::borrow::Cow; +#[cfg(not(feature = "std"))] +use alloc::format; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use core::{ + convert::TryFrom, fmt, iter::FusedIterator, marker::PhantomData, ops::Shl, str::FromStr, +}; + +#[cfg(feature = "bigint")] +use num_bigint::BigUint; +use num_traits::Num; + +/// An error for OID parsing functions. +#[derive(Debug)] +pub enum OidParseError { + TooShort, + /// Signalizes that the first or second component is too large. + /// The first must be within the range 0 to 6 (inclusive). + /// The second component must be less than 40. + FirstComponentsTooLarge, + ParseIntError, +} + +/// Object ID (OID) representation which can be relative or non-relative. +/// An example for an OID in string representation is `"1.2.840.113549.1.1.5"`. +/// +/// For non-relative OIDs restrictions apply to the first two components. +/// +/// This library contains a procedural macro `oid` which can be used to +/// create oids. For example `oid!(1.2.44.233)` or `oid!(rel 44.233)` +/// for relative oids. See the [module documentation](index.html) for more information. +#[derive(Hash, PartialEq, Eq, Clone)] + +pub struct Oid<'a> { + asn1: Cow<'a, [u8]>, + relative: bool, +} + +impl<'a> TryFrom<Any<'a>> for Oid<'a> { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<Self> { + TryFrom::try_from(&any) + } +} + +impl<'a, 'b> TryFrom<&'b Any<'a>> for Oid<'a> { + type Error = Error; + + fn try_from(any: &'b Any<'a>) -> Result<Self> { + // check that any.data.last().unwrap() >> 7 == 0u8 + let asn1 = Cow::Borrowed(any.data); + Ok(Oid::new(asn1)) + } +} + +impl<'a> CheckDerConstraints for Oid<'a> { + fn check_constraints(any: &Any) -> Result<()> { + any.header.assert_primitive()?; + any.header.length.assert_definite()?; + Ok(()) + } +} + +impl DerAutoDerive for Oid<'_> {} + +impl<'a> Tagged for Oid<'a> { + const TAG: Tag = Tag::Oid; +} + +#[cfg(feature = "std")] +impl ToDer for Oid<'_> { + fn to_der_len(&self) -> Result<usize> { + // OID/REL-OID tag will not change header size, so we don't care here + let header = Header::new( + Class::Universal, + false, + Self::TAG, + Length::Definite(self.asn1.len()), + ); + Ok(header.to_der_len()? + self.asn1.len()) + } + + fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let tag = if self.relative { + Tag::RelativeOid + } else { + Tag::Oid + }; + let header = Header::new( + Class::Universal, + false, + tag, + Length::Definite(self.asn1.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.asn1).map_err(Into::into) + } +} + +fn encode_relative(ids: &'_ [u64]) -> impl Iterator<Item = u8> + '_ { + ids.iter().flat_map(|id| { + let bit_count = 64 - id.leading_zeros(); + let octets_needed = ((bit_count + 6) / 7).max(1); + (0..octets_needed).map(move |i| { + let flag = if i == octets_needed - 1 { 0 } else { 1 << 7 }; + ((id >> (7 * (octets_needed - 1 - i))) & 0b111_1111) as u8 | flag + }) + }) +} + +impl<'a> Oid<'a> { + /// Create an OID from the ASN.1 DER encoded form. See the [module documentation](index.html) + /// for other ways to create oids. + pub const fn new(asn1: Cow<'a, [u8]>) -> Oid { + Oid { + asn1, + relative: false, + } + } + + /// Create a relative OID from the ASN.1 DER encoded form. See the [module documentation](index.html) + /// for other ways to create relative oids. + pub const fn new_relative(asn1: Cow<'a, [u8]>) -> Oid { + Oid { + asn1, + relative: true, + } + } + + /// Build an OID from an array of object identifier components. + /// This method allocates memory on the heap. + pub fn from(s: &[u64]) -> core::result::Result<Oid<'static>, OidParseError> { + if s.len() < 2 { + if s.len() == 1 && s[0] == 0 { + return Ok(Oid { + asn1: Cow::Borrowed(&[0]), + relative: false, + }); + } + return Err(OidParseError::TooShort); + } + if s[0] >= 7 || s[1] >= 40 { + return Err(OidParseError::FirstComponentsTooLarge); + } + let asn1_encoded: Vec<u8> = [(s[0] * 40 + s[1]) as u8] + .iter() + .copied() + .chain(encode_relative(&s[2..])) + .collect(); + Ok(Oid { + asn1: Cow::from(asn1_encoded), + relative: false, + }) + } + + /// Build a relative OID from an array of object identifier components. + pub fn from_relative(s: &[u64]) -> core::result::Result<Oid<'static>, OidParseError> { + if s.is_empty() { + return Err(OidParseError::TooShort); + } + let asn1_encoded: Vec<u8> = encode_relative(s).collect(); + Ok(Oid { + asn1: Cow::from(asn1_encoded), + relative: true, + }) + } + + /// Create a deep copy of the oid. + /// + /// This method allocates data on the heap. The returned oid + /// can be used without keeping the ASN.1 representation around. + /// + /// Cloning the returned oid does again allocate data. + pub fn to_owned(&self) -> Oid<'static> { + Oid { + asn1: Cow::from(self.asn1.to_vec()), + relative: self.relative, + } + } + + /// Get the encoded oid without the header. + #[inline] + pub fn as_bytes(&self) -> &[u8] { + self.asn1.as_ref() + } + + /// Get the encoded oid without the header. + #[deprecated(since = "0.2.0", note = "Use `as_bytes` instead")] + #[inline] + pub fn bytes(&self) -> &[u8] { + self.as_bytes() + } + + /// Get the bytes representation of the encoded oid + pub fn into_cow(self) -> Cow<'a, [u8]> { + self.asn1 + } + + /// Convert the OID to a string representation. + /// The string contains the IDs separated by dots, for ex: "1.2.840.113549.1.1.5" + #[cfg(feature = "bigint")] + pub fn to_id_string(&self) -> String { + let ints: Vec<String> = self.iter_bigint().map(|i| i.to_string()).collect(); + ints.join(".") + } + + #[cfg(not(feature = "bigint"))] + /// Convert the OID to a string representation. + /// + /// If every arc fits into a u64 a string like "1.2.840.113549.1.1.5" + /// is returned, otherwise a hex representation. + /// + /// See also the "bigint" feature of this crate. + pub fn to_id_string(&self) -> String { + if let Some(arcs) = self.iter() { + let ints: Vec<String> = arcs.map(|i| i.to_string()).collect(); + ints.join(".") + } else { + let mut ret = String::with_capacity(self.asn1.len() * 3); + for (i, o) in self.asn1.iter().enumerate() { + ret.push_str(&format!("{:02x}", o)); + if i + 1 != self.asn1.len() { + ret.push(' '); + } + } + ret + } + } + + /// Return an iterator over the sub-identifiers (arcs). + #[cfg(feature = "bigint")] + pub fn iter_bigint( + &'_ self, + ) -> impl Iterator<Item = BigUint> + FusedIterator + ExactSizeIterator + '_ { + SubIdentifierIterator { + oid: self, + pos: 0, + first: false, + n: PhantomData, + } + } + + /// Return an iterator over the sub-identifiers (arcs). + /// Returns `None` if at least one arc does not fit into `u64`. + pub fn iter( + &'_ self, + ) -> Option<impl Iterator<Item = u64> + FusedIterator + ExactSizeIterator + '_> { + // Check that every arc fits into u64 + let bytes = if self.relative { + &self.asn1 + } else if self.asn1.is_empty() { + &[] + } else { + &self.asn1[1..] + }; + let max_bits = bytes + .iter() + .fold((0usize, 0usize), |(max, cur), c| { + let is_end = (c >> 7) == 0u8; + if is_end { + (max.max(cur + 7), 0) + } else { + (max, cur + 7) + } + }) + .0; + if max_bits > 64 { + return None; + } + + Some(SubIdentifierIterator { + oid: self, + pos: 0, + first: false, + n: PhantomData, + }) + } + + pub fn from_ber_relative(bytes: &'a [u8]) -> ParseResult<'a, Self> { + let (rem, any) = Any::from_ber(bytes)?; + any.header.assert_primitive()?; + any.header.assert_tag(Tag::RelativeOid)?; + let asn1 = Cow::Borrowed(any.data); + Ok((rem, Oid::new_relative(asn1))) + } + + pub fn from_der_relative(bytes: &'a [u8]) -> ParseResult<'a, Self> { + let (rem, any) = Any::from_der(bytes)?; + any.header.assert_tag(Tag::RelativeOid)?; + Self::check_constraints(&any)?; + let asn1 = Cow::Borrowed(any.data); + Ok((rem, Oid::new_relative(asn1))) + } + + /// Returns true if `needle` is a prefix of the OID. + pub fn starts_with(&self, needle: &Oid) -> bool { + self.asn1.len() >= needle.asn1.len() && self.asn1.starts_with(needle.as_bytes()) + } +} + +trait Repr: Num + Shl<usize, Output = Self> + From<u8> {} +impl<N> Repr for N where N: Num + Shl<usize, Output = N> + From<u8> {} + +struct SubIdentifierIterator<'a, N: Repr> { + oid: &'a Oid<'a>, + pos: usize, + first: bool, + n: PhantomData<&'a N>, +} + +impl<'a, N: Repr> Iterator for SubIdentifierIterator<'a, N> { + type Item = N; + + fn next(&mut self) -> Option<Self::Item> { + use num_traits::identities::Zero; + + if self.pos == self.oid.asn1.len() { + return None; + } + if !self.oid.relative { + if !self.first { + debug_assert!(self.pos == 0); + self.first = true; + return Some((self.oid.asn1[0] / 40).into()); + } else if self.pos == 0 { + self.pos += 1; + if self.oid.asn1[0] == 0 && self.oid.asn1.len() == 1 { + return None; + } + return Some((self.oid.asn1[0] % 40).into()); + } + } + // decode objet sub-identifier according to the asn.1 standard + let mut res = <N as Zero>::zero(); + for o in self.oid.asn1[self.pos..].iter() { + self.pos += 1; + res = (res << 7) + (o & 0b111_1111).into(); + let flag = o >> 7; + if flag == 0u8 { + break; + } + } + Some(res) + } +} + +impl<'a, N: Repr> FusedIterator for SubIdentifierIterator<'a, N> {} + +impl<'a, N: Repr> ExactSizeIterator for SubIdentifierIterator<'a, N> { + fn len(&self) -> usize { + if self.oid.relative { + self.oid.asn1.iter().filter(|o| (*o >> 7) == 0u8).count() + } else if self.oid.asn1.len() == 0 { + 0 + } else if self.oid.asn1.len() == 1 { + if self.oid.asn1[0] == 0 { + 1 + } else { + 2 + } + } else { + 2 + self.oid.asn1[2..] + .iter() + .filter(|o| (*o >> 7) == 0u8) + .count() + } + } + + #[cfg(feature = "exact_size_is_empty")] + fn is_empty(&self) -> bool { + self.oid.asn1.is_empty() + } +} + +impl<'a> fmt::Display for Oid<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.relative { + f.write_str("rel. ")?; + } + f.write_str(&self.to_id_string()) + } +} + +impl<'a> fmt::Debug for Oid<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("OID(")?; + <Oid as fmt::Display>::fmt(self, f)?; + f.write_str(")") + } +} + +impl<'a> FromStr for Oid<'a> { + type Err = OidParseError; + + fn from_str(s: &str) -> core::result::Result<Self, Self::Err> { + let v: core::result::Result<Vec<_>, _> = s.split('.').map(|c| c.parse::<u64>()).collect(); + v.map_err(|_| OidParseError::ParseIntError) + .and_then(|v| Oid::from(&v)) + } +} + +/// Helper macro to declare integers at compile-time +/// +/// Since the DER encoded oids are not very readable we provide a +/// procedural macro `oid!`. The macro can be used the following ways: +/// +/// - `oid!(1.4.42.23)`: Create a const expression for the corresponding `Oid<'static>` +/// - `oid!(rel 42.23)`: Create a const expression for the corresponding relative `Oid<'static>` +/// - `oid!(raw 1.4.42.23)`/`oid!(raw rel 42.23)`: Obtain the DER encoded form as a byte array. +/// +/// # Comparing oids +/// +/// Comparing a parsed oid to a static oid is probably the most common +/// thing done with oids in your code. The `oid!` macro can be used in expression positions for +/// this purpose. For example +/// ``` +/// use asn1_rs::{oid, Oid}; +/// +/// # let some_oid: Oid<'static> = oid!(1.2.456); +/// const SOME_STATIC_OID: Oid<'static> = oid!(1.2.456); +/// assert_eq!(some_oid, SOME_STATIC_OID) +/// ``` +/// To get a relative Oid use `oid!(rel 1.2)`. +/// +/// Because of limitations for procedural macros ([rust issue](https://github.com/rust-lang/rust/issues/54727)) +/// and constants used in patterns ([rust issue](https://github.com/rust-lang/rust/issues/31434)) +/// the `oid` macro can not directly be used in patterns, also not through constants. +/// You can do this, though: +/// ``` +/// # use asn1_rs::{oid, Oid}; +/// # let some_oid: Oid<'static> = oid!(1.2.456); +/// const SOME_OID: Oid<'static> = oid!(1.2.456); +/// if some_oid == SOME_OID || some_oid == oid!(1.2.456) { +/// println!("match"); +/// } +/// +/// // Alternatively, compare the DER encoded form directly: +/// const SOME_OID_RAW: &[u8] = &oid!(raw 1.2.456); +/// match some_oid.as_bytes() { +/// SOME_OID_RAW => println!("match"), +/// _ => panic!("no match"), +/// } +/// ``` +/// *Attention*, be aware that the latter version might not handle the case of a relative oid correctly. An +/// extra check might be necessary. +#[macro_export] +macro_rules! oid { + (raw $items:expr) => { + $crate::exports::asn1_rs_impl::encode_oid!($items) + }; + (rel $items:expr) => { + $crate::Oid::new_relative($crate::exports::borrow::Cow::Borrowed( + &$crate::exports::asn1_rs_impl::encode_oid!(rel $items), + )) + }; + ($items:expr) => { + $crate::Oid::new($crate::exports::borrow::Cow::Borrowed( + &$crate::oid!(raw $items), + )) + }; +} + +#[cfg(test)] +mod tests { + use crate::{FromDer, Oid, ToDer}; + use hex_literal::hex; + + #[test] + fn declare_oid() { + let oid = super::oid! {1.2.840.113549.1}; + assert_eq!(oid.to_string(), "1.2.840.113549.1"); + } + + const OID_RSA_ENCRYPTION: &[u8] = &oid! {raw 1.2.840.113549.1.1.1}; + const OID_EC_PUBLIC_KEY: &[u8] = &oid! {raw 1.2.840.10045.2.1}; + #[allow(clippy::match_like_matches_macro)] + fn compare_oid(oid: &Oid) -> bool { + match oid.as_bytes() { + OID_RSA_ENCRYPTION => true, + OID_EC_PUBLIC_KEY => true, + _ => false, + } + } + + #[test] + fn test_compare_oid() { + let oid = Oid::from(&[1, 2, 840, 113_549, 1, 1, 1]).unwrap(); + assert_eq!(oid, oid! {1.2.840.113549.1.1.1}); + let oid = Oid::from(&[1, 2, 840, 113_549, 1, 1, 1]).unwrap(); + assert!(compare_oid(&oid)); + } + + #[test] + fn oid_to_der() { + let oid = super::oid! {1.2.840.113549.1}; + assert_eq!(oid.to_der_len(), Ok(9)); + let v = oid.to_der_vec().expect("could not serialize"); + assert_eq!(&v, &hex! {"06 07 2a 86 48 86 f7 0d 01"}); + let (_, oid2) = Oid::from_der(&v).expect("could not re-parse"); + assert_eq!(&oid, &oid2); + } + + #[test] + fn oid_starts_with() { + const OID_RSA_ENCRYPTION: Oid = oid! {1.2.840.113549.1.1.1}; + const OID_EC_PUBLIC_KEY: Oid = oid! {1.2.840.10045.2.1}; + let oid = super::oid! {1.2.840.113549.1}; + assert!(OID_RSA_ENCRYPTION.starts_with(&oid)); + assert!(!OID_EC_PUBLIC_KEY.starts_with(&oid)); + } +} diff --git a/src/asn1_types/optional.rs b/src/asn1_types/optional.rs new file mode 100644 index 0000000..a8027ab --- /dev/null +++ b/src/asn1_types/optional.rs @@ -0,0 +1,87 @@ +use crate::*; + +// note: we cannot implement `TryFrom<Any<'a>> with generic errors for Option<T>`, +// because this conflicts with generic `T` implementation in +// `src/traits.rs`, since `T` always satisfies `T: Into<Option<T>>` +// +// for the same reason, we cannot use a generic error type here +impl<'a, T> FromBer<'a> for Option<T> +where + T: FromBer<'a>, +{ + fn from_ber(bytes: &'a [u8]) -> ParseResult<Self> { + if bytes.is_empty() { + return Ok((bytes, None)); + } + match T::from_ber(bytes) { + Ok((rem, t)) => Ok((rem, Some(t))), + Err(nom::Err::Error(Error::UnexpectedTag { .. })) => Ok((bytes, None)), + Err(e) => Err(e), + } + } +} + +impl<'a, T> FromDer<'a> for Option<T> +where + T: FromDer<'a>, +{ + fn from_der(bytes: &'a [u8]) -> ParseResult<Self> { + if bytes.is_empty() { + return Ok((bytes, None)); + } + match T::from_der(bytes) { + Ok((rem, t)) => Ok((rem, Some(t))), + Err(nom::Err::Error(Error::UnexpectedTag { .. })) => Ok((bytes, None)), + Err(e) => Err(e), + } + } +} + +impl<T> CheckDerConstraints for Option<T> +where + T: CheckDerConstraints, +{ + fn check_constraints(any: &Any) -> Result<()> { + T::check_constraints(any) + } +} + +impl<T> DynTagged for Option<T> +where + T: DynTagged, +{ + fn tag(&self) -> Tag { + if self.is_some() { + self.tag() + } else { + Tag(0) + } + } +} + +#[cfg(feature = "std")] +impl<T> ToDer for Option<T> +where + T: ToDer, +{ + fn to_der_len(&self) -> Result<usize> { + match self { + None => Ok(0), + Some(t) => t.to_der_len(), + } + } + + fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + match self { + None => Ok(0), + Some(t) => t.write_der_header(writer), + } + } + + fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + match self { + None => Ok(0), + Some(t) => t.write_der_content(writer), + } + } +} diff --git a/src/asn1_types/real.rs b/src/asn1_types/real.rs new file mode 100644 index 0000000..db72a0d --- /dev/null +++ b/src/asn1_types/real.rs @@ -0,0 +1,462 @@ +use crate::*; +use alloc::format; +use core::convert::TryFrom; +use nom::Needed; + +mod f32; +mod f64; +pub use self::f32::*; +pub use self::f64::*; + +/// ASN.1 `REAL` type +/// +/// # Limitations +/// +/// When encoding binary values, only base 2 is supported +#[derive(Debug, PartialEq)] +pub enum Real { + /// Non-special values + Binary { + mantissa: f64, + base: u32, + exponent: i32, + enc_base: u8, + }, + /// Infinity (∞). + Infinity, + /// Negative infinity (−∞). + NegInfinity, + /// Zero + Zero, +} + +impl Real { + /// Create a new `REAL` from the `f64` value. + pub fn new(f: f64) -> Self { + if f.is_infinite() { + if f.is_sign_positive() { + Self::Infinity + } else { + Self::NegInfinity + } + } else if f.abs() == 0.0 { + Self::Zero + } else { + let mut e = 0; + let mut f = f; + while f.fract() != 0.0 { + f *= 10.0_f64; + e -= 1; + } + Real::Binary { + mantissa: f, + base: 10, + exponent: e, + enc_base: 10, + } + .normalize_base10() + } + } + + pub const fn with_enc_base(self, enc_base: u8) -> Self { + match self { + Real::Binary { + mantissa, + base, + exponent, + .. + } => Real::Binary { + mantissa, + base, + exponent, + enc_base, + }, + e => e, + } + } + + fn normalize_base10(self) -> Self { + match self { + Real::Binary { + mantissa, + base: 10, + exponent, + enc_base: _enc_base, + } => { + let mut m = mantissa; + let mut e = exponent; + while m.abs() > f64::EPSILON && m.rem_euclid(10.0).abs() < f64::EPSILON { + m /= 10.0; + e += 1; + } + Real::Binary { + mantissa: m, + base: 10, + exponent: e, + enc_base: _enc_base, + } + } + _ => self, + } + } + + /// Create a new binary `REAL` + #[inline] + pub const fn binary(mantissa: f64, base: u32, exponent: i32) -> Self { + Self::Binary { + mantissa, + base, + exponent, + enc_base: 2, + } + } + + /// Returns `true` if this value is positive infinity or negative infinity, and + /// `false` otherwise. + #[inline] + pub fn is_infinite(&self) -> bool { + matches!(self, Real::Infinity | Real::NegInfinity) + } + + /// Returns `true` if this number is not infinite. + #[inline] + pub fn is_finite(&self) -> bool { + matches!(self, Real::Zero | Real::Binary { .. }) + } + + /// Returns the 'f64' value of this `REAL`. + /// + /// Returned value is a float, and may be infinite. + pub fn f64(&self) -> f64 { + match self { + Real::Binary { + mantissa, + base, + exponent, + .. + } => { + let f = mantissa; + let exp = (*base as f64).powi(*exponent); + f * exp + } + Real::Zero => 0.0_f64, + Real::Infinity => f64::INFINITY, + Real::NegInfinity => f64::NEG_INFINITY, + } + } + + /// Returns the 'f32' value of this `REAL`. + /// + /// This functions casts the result of [`Real::f64`] to a `f32`, and loses precision. + pub fn f32(&self) -> f32 { + self.f64() as f32 + } +} + +impl<'a> TryFrom<Any<'a>> for Real { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<Self> { + any.tag().assert_eq(Self::TAG)?; + any.header.assert_primitive()?; + let data = &any.data; + if data.is_empty() { + return Ok(Real::Zero); + } + // code inspired from pyasn1 + let first = data[0]; + let rem = &data[1..]; + if first & 0x80 != 0 { + // binary encoding (X.690 section 8.5.6) + let rem = rem; + // format of exponent + let (n, rem) = match first & 0x03 { + 4 => { + let (b, rem) = rem + .split_first() + .ok_or_else(|| Error::Incomplete(Needed::new(1)))?; + (*b as usize, rem) + } + b => (b as usize + 1, rem), + }; + if n >= rem.len() { + return Err(any.tag().invalid_value("Invalid float value(exponent)")); + } + // n cannot be 0 (see the +1 above) + let (eo, rem) = rem.split_at(n); + // so 'eo' cannot be empty + let mut e = if eo[0] & 0x80 != 0 { -1 } else { 0 }; + // safety check: 'eo' length must be <= container type for 'e' + if eo.len() > 4 { + return Err(any.tag().invalid_value("Exponent too large (REAL)")); + } + for b in eo { + e = (e << 8) | (*b as i32); + } + // base bits + let b = (first >> 4) & 0x03; + let _enc_base = match b { + 0 => 2, + 1 => 8, + 2 => 16, + _ => return Err(any.tag().invalid_value("Illegal REAL encoding base")), + }; + let e = match b { + // base 2 + 0 => e, + // base 8 + 1 => e * 3, + // base 16 + 2 => e * 4, + _ => return Err(any.tag().invalid_value("Illegal REAL base")), + }; + if rem.len() > 8 { + return Err(any.tag().invalid_value("Mantissa too large (REAL)")); + } + let mut p = 0; + for b in rem { + p = (p << 8) | (*b as i64); + } + // sign bit + let p = if first & 0x40 != 0 { -p } else { p }; + // scale bits + let sf = (first >> 2) & 0x03; + let p = match sf { + 0 => p as f64, + sf => { + // 2^sf: cannot overflow, sf is between 0 and 3 + let scale = 2_f64.powi(sf as _); + (p as f64) * scale + } + }; + Ok(Real::Binary { + mantissa: p, + base: 2, + exponent: e, + enc_base: _enc_base, + }) + } else if first & 0x40 != 0 { + // special real value (X.690 section 8.5.8) + // there shall be only one contents octet, + if any.header.length != Length::Definite(1) { + return Err(Error::InvalidLength); + } + // with values as follows + match first { + 0x40 => Ok(Real::Infinity), + 0x41 => Ok(Real::NegInfinity), + _ => Err(any.tag().invalid_value("Invalid float special value")), + } + } else { + // decimal encoding (X.690 section 8.5.7) + let s = alloc::str::from_utf8(rem)?; + match first & 0x03 { + 0x1 => { + // NR1 + match s.parse::<u32>() { + Err(_) => Err(any.tag().invalid_value("Invalid float string encoding")), + Ok(v) => Ok(Real::new(v.into())), + } + } + 0x2 /* NR2 */ | 0x3 /* NR3 */=> { + match s.parse::<f64>() { + Err(_) => Err(any.tag().invalid_value("Invalid float string encoding")), + Ok(v) => Ok(Real::new(v)), + } + } + c => { + Err(any.tag().invalid_value(&format!("Invalid NR ({})", c))) + } + } + } + } +} + +impl CheckDerConstraints for Real { + fn check_constraints(any: &Any) -> Result<()> { + any.header.assert_primitive()?; + any.header.length.assert_definite()?; + // XXX more checks + Ok(()) + } +} + +impl DerAutoDerive for Real {} + +impl Tagged for Real { + const TAG: Tag = Tag::RealType; +} + +#[cfg(feature = "std")] +impl ToDer for Real { + fn to_der_len(&self) -> Result<usize> { + match self { + Real::Zero => Ok(0), + Real::Infinity | Real::NegInfinity => Ok(1), + Real::Binary { .. } => { + let mut sink = std::io::sink(); + let n = self + .write_der_content(&mut sink) + .map_err(|_| Self::TAG.invalid_value("Serialization of REAL failed"))?; + Ok(n) + } + } + } + + 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.to_der_len()?), + ); + header.write_der_header(writer).map_err(Into::into) + } + + fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + match self { + Real::Zero => Ok(0), + Real::Infinity => writer.write(&[0x40]).map_err(Into::into), + Real::NegInfinity => writer.write(&[0x41]).map_err(Into::into), + Real::Binary { + mantissa, + base, + exponent, + enc_base: _enc_base, + } => { + if *base == 10 { + // using character form + let sign = if *exponent == 0 { "+" } else { "" }; + let s = format!("\x03{}E{}{}", mantissa, sign, exponent); + return writer.write(s.as_bytes()).map_err(Into::into); + } + if *base != 2 { + return Err(Self::TAG.invalid_value("Invalid base for REAL").into()); + } + let mut first: u8 = 0x80; + // choose encoding base + let enc_base = *_enc_base; + let (ms, mut m, enc_base, mut e) = + drop_floating_point(*mantissa, enc_base, *exponent); + assert!(m != 0); + if ms < 0 { + first |= 0x40 + }; + // exponent & mantissa normalization + match enc_base { + 2 => { + while m & 0x1 == 0 { + m >>= 1; + e += 1; + } + } + 8 => { + while m & 0x7 == 0 { + m >>= 3; + e += 1; + } + first |= 0x10; + } + _ /* 16 */ => { + while m & 0xf == 0 { + m >>= 4; + e += 1; + } + first |= 0x20; + } + } + // scale factor + // XXX in DER, sf is always 0 (11.3.1) + let mut sf = 0; + while m & 0x1 == 0 && sf < 4 { + m >>= 1; + sf += 1; + } + first |= sf << 2; + // exponent length and bytes + let len_e = match e.abs() { + 0..=0xff => 1, + 0x100..=0xffff => 2, + 0x1_0000..=0xff_ffff => 3, + // e is an `i32` so it can't be longer than 4 bytes + // use 4, so `first` is ORed with 3 + _ => 4, + }; + first |= (len_e - 1) & 0x3; + // write first byte + let mut n = writer.write(&[first])?; + // write exponent + // special case: number of bytes from exponent is > 3 and cannot fit in 2 bits + #[allow(clippy::identity_op)] + if len_e == 4 { + let b = len_e & 0xff; + n += writer.write(&[b])?; + } + // we only need to write e.len() bytes + let bytes = e.to_be_bytes(); + n += writer.write(&bytes[(4 - len_e) as usize..])?; + // write mantissa + let bytes = m.to_be_bytes(); + let mut idx = 0; + for &b in bytes.iter() { + if b != 0 { + break; + } + idx += 1; + } + n += writer.write(&bytes[idx..])?; + Ok(n) + } + } + } +} + +impl From<f32> for Real { + fn from(f: f32) -> Self { + Real::new(f.into()) + } +} + +impl From<f64> for Real { + fn from(f: f64) -> Self { + Real::new(f) + } +} + +impl From<Real> for f32 { + fn from(r: Real) -> Self { + r.f32() + } +} + +impl From<Real> for f64 { + fn from(r: Real) -> Self { + r.f64() + } +} + +#[cfg(feature = "std")] +fn drop_floating_point(m: f64, b: u8, e: i32) -> (i8, u64, u8, i32) { + let ms = if m.is_sign_positive() { 1 } else { -1 }; + let es = if e.is_positive() { 1 } else { -1 }; + let mut m = m.abs(); + let mut e = e; + // + if b == 8 { + m *= 2_f64.powi((e.abs() / 3) * es); + e = (e.abs() / 3) * es; + } else if b == 16 { + m *= 2_f64.powi((e.abs() / 4) * es); + e = (e.abs() / 4) * es; + } + // + while m.abs() > f64::EPSILON { + if m.fract() != 0.0 { + m *= b as f64; + e -= 1; + } else { + break; + } + } + (ms, m as u64, b, e) +} diff --git a/src/asn1_types/real/f32.rs b/src/asn1_types/real/f32.rs new file mode 100644 index 0000000..cb41dbf --- /dev/null +++ b/src/asn1_types/real/f32.rs @@ -0,0 +1,27 @@ +use crate::{Any, CheckDerConstraints, DerAutoDerive, Error, Real, Result, Tag, Tagged}; +use core::convert::{TryFrom, TryInto}; + +impl<'a> TryFrom<Any<'a>> for f32 { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<f32> { + any.tag().assert_eq(Self::TAG)?; + any.header.assert_primitive()?; + let real: Real = any.try_into()?; + Ok(real.f32()) + } +} + +impl CheckDerConstraints for f32 { + fn check_constraints(any: &Any) -> Result<()> { + any.header.assert_primitive()?; + any.header.length.assert_definite()?; + Ok(()) + } +} + +impl DerAutoDerive for f32 {} + +impl Tagged for f32 { + const TAG: Tag = Tag::RealType; +} diff --git a/src/asn1_types/real/f64.rs b/src/asn1_types/real/f64.rs new file mode 100644 index 0000000..4986d9b --- /dev/null +++ b/src/asn1_types/real/f64.rs @@ -0,0 +1,27 @@ +use crate::{Any, CheckDerConstraints, DerAutoDerive, Error, Real, Result, Tag, Tagged}; +use core::convert::{TryFrom, TryInto}; + +impl<'a> TryFrom<Any<'a>> for f64 { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<f64> { + any.tag().assert_eq(Self::TAG)?; + any.header.assert_primitive()?; + let real: Real = any.try_into()?; + Ok(real.f64()) + } +} + +impl CheckDerConstraints for f64 { + fn check_constraints(any: &Any) -> Result<()> { + any.header.assert_primitive()?; + any.header.length.assert_definite()?; + Ok(()) + } +} + +impl DerAutoDerive for f64 {} + +impl Tagged for f64 { + const TAG: Tag = Tag::RealType; +} diff --git a/src/asn1_types/sequence.rs b/src/asn1_types/sequence.rs new file mode 100644 index 0000000..3a65ec0 --- /dev/null +++ b/src/asn1_types/sequence.rs @@ -0,0 +1,398 @@ +use crate::*; +use alloc::borrow::Cow; +use alloc::vec::Vec; +use core::convert::TryFrom; + +mod iterator; +mod sequence_of; +mod vec; + +pub use iterator::*; +pub use sequence_of::*; +pub use vec::*; + +/// The `SEQUENCE` object is an ordered list of heteregeneous types. +/// +/// Sequences can usually be of 2 types: +/// - a list of different objects (`SEQUENCE`, usually parsed as a `struct`) +/// - a list of similar objects (`SEQUENCE OF`, usually parsed as a `Vec<T>`) +/// +/// The current object covers the former. For the latter, see the [`SequenceOf`] documentation. +/// +/// The `Sequence` object contains the (*unparsed*) encoded representation of its content. It provides +/// methods to parse and iterate contained objects, or convert the sequence to other types. +/// +/// # Building a Sequence +/// +/// To build a DER sequence: +/// - if the sequence is composed of objects of the same type, the [`Sequence::from_iter_to_der`] method can be used +/// - otherwise, the [`ToDer`] trait can be used to create content incrementally +/// +/// ``` +/// use asn1_rs::{Integer, Sequence, SerializeResult, ToDer}; +/// +/// fn build_seq<'a>() -> SerializeResult<Sequence<'a>> { +/// let mut v = Vec::new(); +/// // add an Integer object (construct type): +/// let i = Integer::from_u32(4); +/// let _ = i.write_der(&mut v)?; +/// // some primitive objects also implement `ToDer`. A string will be mapped as `Utf8String`: +/// let _ = "abcd".write_der(&mut v)?; +/// // return the sequence built from the DER content +/// Ok(Sequence::new(v.into())) +/// } +/// +/// let seq = build_seq().unwrap(); +/// +/// ``` +/// +/// # Examples +/// +/// ``` +/// use asn1_rs::{Error, Sequence}; +/// +/// // build sequence +/// let it = [2, 3, 4].iter(); +/// let seq = Sequence::from_iter_to_der(it).unwrap(); +/// +/// // `seq` now contains the serialized DER representation of the array +/// +/// // iterate objects +/// let mut sum = 0; +/// for item in seq.der_iter::<u32, Error>() { +/// // item has type `Result<u32>`, since parsing the serialized bytes could fail +/// sum += item.expect("parsing list item failed"); +/// } +/// assert_eq!(sum, 9); +/// +/// ``` +/// +/// Note: the above example encodes a `SEQUENCE OF INTEGER` object, the [`SequenceOf`] object could +/// be used to provide a simpler API. +/// +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Sequence<'a> { + /// Serialized DER representation of the sequence content + pub content: Cow<'a, [u8]>, +} + +impl<'a> Sequence<'a> { + /// Build a sequence, given the provided content + pub const fn new(content: Cow<'a, [u8]>) -> Self { + Sequence { content } + } + + /// Consume the sequence and return the content + #[inline] + pub fn into_content(self) -> Cow<'a, [u8]> { + self.content + } + + /// Apply the parsing function to the sequence content, consuming the sequence + /// + /// Note: this function expects the caller to take ownership of content. + /// In some cases, handling the lifetime of objects is not easy (when keeping only references on + /// data). Other methods are provided (depending on the use case): + /// - [`Sequence::parse`] takes a reference on the sequence data, but does not consume it, + /// - [`Sequence::from_der_and_then`] does the parsing of the sequence and applying the function + /// in one step, ensuring there are only references (and dropping the temporary sequence). + pub fn and_then<U, F, E>(self, op: F) -> ParseResult<'a, U, E> + where + F: FnOnce(Cow<'a, [u8]>) -> ParseResult<U, E>, + { + op(self.content) + } + + /// Same as [`Sequence::from_der_and_then`], but using BER encoding (no constraints). + pub fn from_ber_and_then<U, F, E>(bytes: &'a [u8], op: F) -> ParseResult<'a, U, E> + where + F: FnOnce(&'a [u8]) -> ParseResult<U, E>, + E: From<Error>, + { + let (rem, seq) = Sequence::from_ber(bytes).map_err(Err::convert)?; + let data = match seq.content { + Cow::Borrowed(b) => b, + // Since 'any' is built from 'bytes', it is borrowed by construction + Cow::Owned(_) => unreachable!(), + }; + let (_, res) = op(data)?; + Ok((rem, res)) + } + + /// Parse a DER sequence and apply the provided parsing function to content + /// + /// After parsing, the sequence object and header are discarded. + /// + /// ``` + /// use asn1_rs::{FromDer, ParseResult, Sequence}; + /// + /// // Parse a SEQUENCE { + /// // a INTEGER (0..255), + /// // b INTEGER (0..4294967296) + /// // } + /// // and return only `(a,b) + /// fn parser(i: &[u8]) -> ParseResult<(u8, u32)> { + /// Sequence::from_der_and_then(i, |i| { + /// let (i, a) = u8::from_der(i)?; + /// let (i, b) = u32::from_der(i)?; + /// Ok((i, (a, b))) + /// } + /// ) + /// } + /// ``` + pub fn from_der_and_then<U, F, E>(bytes: &'a [u8], op: F) -> ParseResult<'a, U, E> + where + F: FnOnce(&'a [u8]) -> ParseResult<U, E>, + E: From<Error>, + { + let (rem, seq) = Sequence::from_der(bytes).map_err(Err::convert)?; + let data = match seq.content { + Cow::Borrowed(b) => b, + // Since 'any' is built from 'bytes', it is borrowed by construction + Cow::Owned(_) => unreachable!(), + }; + let (_, res) = op(data)?; + Ok((rem, res)) + } + + /// Apply the parsing function to the sequence content (non-consuming version) + pub fn parse<F, T, E>(&'a self, mut f: F) -> ParseResult<'a, T, E> + where + F: FnMut(&'a [u8]) -> ParseResult<'a, T, E>, + { + let input: &[u8] = &self.content; + f(input) + } + + /// Apply the parsing function to the sequence content (consuming version) + /// + /// Note: to parse and apply a parsing function in one step, use the + /// [`Sequence::from_der_and_then`] method. + /// + /// # Limitations + /// + /// This function fails if the sequence contains `Owned` data, because the parsing function + /// takes a reference on data (which is dropped). + pub fn parse_into<F, T, E>(self, mut f: F) -> ParseResult<'a, T, E> + where + F: FnMut(&'a [u8]) -> ParseResult<'a, T, E>, + E: From<Error>, + { + match self.content { + Cow::Borrowed(b) => f(b), + _ => Err(nom::Err::Error(Error::LifetimeError.into())), + } + } + + /// Return an iterator over the sequence content, attempting to decode objects as BER + /// + /// This method can be used when all objects from the sequence have the same type. + pub fn ber_iter<T, E>(&'a self) -> SequenceIterator<'a, T, BerParser, E> + where + T: FromBer<'a, E>, + { + SequenceIterator::new(&self.content) + } + + /// Return an iterator over the sequence content, attempting to decode objects as DER + /// + /// This method can be used when all objects from the sequence have the same type. + pub fn der_iter<T, E>(&'a self) -> SequenceIterator<'a, T, DerParser, E> + where + T: FromDer<'a, E>, + { + SequenceIterator::new(&self.content) + } + + /// Attempt to parse the sequence as a `SEQUENCE OF` items (BER), and return the parsed items as a `Vec`. + pub fn ber_sequence_of<T, E>(&'a self) -> Result<Vec<T>, E> + where + T: FromBer<'a, E>, + E: From<Error>, + { + self.ber_iter().collect() + } + + /// Attempt to parse the sequence as a `SEQUENCE OF` items (DER), and return the parsed items as a `Vec`. + pub fn der_sequence_of<T, E>(&'a self) -> Result<Vec<T>, E> + where + T: FromDer<'a, E>, + E: From<Error>, + { + self.der_iter().collect() + } + + /// Attempt to parse the sequence as a `SEQUENCE OF` items (BER) (consuming input), + /// and return the parsed items as a `Vec`. + /// + /// Note: if `Self` is an `Owned` object, the data will be duplicated (causing allocations) into separate objects. + pub fn into_ber_sequence_of<T, U, E>(self) -> Result<Vec<T>, E> + where + for<'b> T: FromBer<'b, E>, + E: From<Error>, + T: ToStatic<Owned = T>, + { + match self.content { + Cow::Borrowed(bytes) => SequenceIterator::<T, BerParser, E>::new(bytes).collect(), + Cow::Owned(data) => { + let v1 = SequenceIterator::<T, BerParser, E>::new(&data) + .collect::<Result<Vec<T>, E>>()?; + let v2 = v1.iter().map(|t| t.to_static()).collect::<Vec<_>>(); + Ok(v2) + } + } + } + + /// Attempt to parse the sequence as a `SEQUENCE OF` items (DER) (consuming input), + /// and return the parsed items as a `Vec`. + /// + /// Note: if `Self` is an `Owned` object, the data will be duplicated (causing allocations) into separate objects. + pub fn into_der_sequence_of<T, U, E>(self) -> Result<Vec<T>, E> + where + for<'b> T: FromDer<'b, E>, + E: From<Error>, + T: ToStatic<Owned = T>, + { + match self.content { + Cow::Borrowed(bytes) => SequenceIterator::<T, DerParser, E>::new(bytes).collect(), + Cow::Owned(data) => { + let v1 = SequenceIterator::<T, DerParser, E>::new(&data) + .collect::<Result<Vec<T>, E>>()?; + let v2 = v1.iter().map(|t| t.to_static()).collect::<Vec<_>>(); + Ok(v2) + } + } + } + + pub fn into_der_sequence_of_ref<T, E>(self) -> Result<Vec<T>, E> + where + T: FromDer<'a, E>, + E: From<Error>, + { + match self.content { + Cow::Borrowed(bytes) => SequenceIterator::<T, DerParser, E>::new(bytes).collect(), + Cow::Owned(_) => Err(Error::LifetimeError.into()), + } + } +} + +impl<'a> ToStatic for Sequence<'a> { + type Owned = Sequence<'static>; + + fn to_static(&self) -> Self::Owned { + Sequence { + content: Cow::Owned(self.content.to_vec()), + } + } +} + +impl<T, U> ToStatic for Vec<T> +where + T: ToStatic<Owned = U>, + U: 'static, +{ + type Owned = Vec<U>; + + fn to_static(&self) -> Self::Owned { + self.iter().map(|t| t.to_static()).collect() + } +} + +impl<'a> AsRef<[u8]> for Sequence<'a> { + fn as_ref(&self) -> &[u8] { + &self.content + } +} + +impl<'a> TryFrom<Any<'a>> for Sequence<'a> { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<Sequence<'a>> { + TryFrom::try_from(&any) + } +} + +impl<'a, 'b> TryFrom<&'b Any<'a>> for Sequence<'a> { + type Error = Error; + + fn try_from(any: &'b Any<'a>) -> Result<Sequence<'a>> { + any.tag().assert_eq(Self::TAG)?; + any.header.assert_constructed()?; + Ok(Sequence { + content: Cow::Borrowed(any.data), + }) + } +} + +impl<'a> CheckDerConstraints for Sequence<'a> { + fn check_constraints(_any: &Any) -> Result<()> { + // TODO: iterate on ANY objects and check constraints? -> this will not be exhaustive + // test, for ex INTEGER encoding will not be checked + Ok(()) + } +} + +impl<'a> DerAutoDerive for Sequence<'a> {} + +impl<'a> Tagged for Sequence<'a> { + const TAG: Tag = Tag::Sequence; +} + +#[cfg(feature = "std")] +impl ToDer for Sequence<'_> { + fn to_der_len(&self) -> Result<usize> { + let sz = self.content.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, + true, + Self::TAG, + Length::Definite(self.content.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.content).map_err(Into::into) + } +} + +#[cfg(feature = "std")] +impl<'a> Sequence<'a> { + /// Attempt to create a `Sequence` from an iterator over serializable objects (to DER) + /// + /// # Examples + /// + /// ``` + /// use asn1_rs::Sequence; + /// + /// // build sequence + /// let it = [2, 3, 4].iter(); + /// let seq = Sequence::from_iter_to_der(it).unwrap(); + /// ``` + pub fn from_iter_to_der<T, IT>(it: IT) -> SerializeResult<Self> + where + IT: Iterator<Item = T>, + T: ToDer, + T: Tagged, + { + let mut v = Vec::new(); + for item in it { + let item_v = <T as ToDer>::to_der_vec(&item)?; + v.extend_from_slice(&item_v); + } + Ok(Sequence { + content: Cow::Owned(v), + }) + } +} diff --git a/src/asn1_types/sequence/iterator.rs b/src/asn1_types/sequence/iterator.rs new file mode 100644 index 0000000..0ff5fc9 --- /dev/null +++ b/src/asn1_types/sequence/iterator.rs @@ -0,0 +1,106 @@ +use crate::{ASN1Parser, BerParser, DerParser, Error, FromBer, FromDer}; +use core::marker::PhantomData; + +/// An Iterator over binary data, parsing elements of type `T` +/// +/// This helps parsing `SEQUENCE OF` items of type `T`. The type of parser +/// (BER/DER) is specified using the generic parameter `F` of this struct. +/// +/// Note: the iterator must start on the sequence *contents*, not the sequence itself. +/// +/// # Examples +/// +/// ```rust +/// use asn1_rs::{DerParser, Integer, SequenceIterator}; +/// +/// let data = &[0x30, 0x6, 0x2, 0x1, 0x1, 0x2, 0x1, 0x2]; +/// for (idx, item) in SequenceIterator::<Integer, DerParser>::new(&data[2..]).enumerate() { +/// let item = item.unwrap(); // parsing could have failed +/// let i = item.as_u32().unwrap(); // integer can be negative, or too large to fit into u32 +/// assert_eq!(i as usize, idx + 1); +/// } +/// ``` +#[derive(Debug)] +pub struct SequenceIterator<'a, T, F, E = Error> +where + F: ASN1Parser, +{ + data: &'a [u8], + has_error: bool, + _t: PhantomData<T>, + _f: PhantomData<F>, + _e: PhantomData<E>, +} + +impl<'a, T, F, E> SequenceIterator<'a, T, F, E> +where + F: ASN1Parser, +{ + pub fn new(data: &'a [u8]) -> Self { + SequenceIterator { + data, + has_error: false, + _t: PhantomData, + _f: PhantomData, + _e: PhantomData, + } + } +} + +impl<'a, T, E> Iterator for SequenceIterator<'a, T, BerParser, E> +where + T: FromBer<'a, E>, + E: From<Error>, +{ + type Item = Result<T, E>; + + fn next(&mut self) -> Option<Self::Item> { + if self.has_error || self.data.is_empty() { + return None; + } + match T::from_ber(self.data) { + Ok((rem, obj)) => { + self.data = rem; + Some(Ok(obj)) + } + Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => { + self.has_error = true; + Some(Err(e)) + } + + Err(nom::Err::Incomplete(n)) => { + self.has_error = true; + Some(Err(Error::Incomplete(n).into())) + } + } + } +} + +impl<'a, T, E> Iterator for SequenceIterator<'a, T, DerParser, E> +where + T: FromDer<'a, E>, + E: From<Error>, +{ + type Item = Result<T, E>; + + fn next(&mut self) -> Option<Self::Item> { + if self.has_error || self.data.is_empty() { + return None; + } + match T::from_der(self.data) { + Ok((rem, obj)) => { + self.data = rem; + Some(Ok(obj)) + } + Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => { + self.has_error = true; + Some(Err(e)) + } + + Err(nom::Err::Incomplete(n)) => { + self.has_error = true; + Some(Err(Error::Incomplete(n).into())) + } + } + } +} diff --git a/src/asn1_types/sequence/sequence_of.rs b/src/asn1_types/sequence/sequence_of.rs new file mode 100644 index 0000000..239e72f --- /dev/null +++ b/src/asn1_types/sequence/sequence_of.rs @@ -0,0 +1,150 @@ +use crate::*; +use alloc::vec::Vec; +use core::convert::TryFrom; +use core::iter::FromIterator; + +/// The `SEQUENCE OF` object is an ordered list of homogeneous types. +/// +/// # Examples +/// +/// ``` +/// use asn1_rs::SequenceOf; +/// use std::iter::FromIterator; +/// +/// // build set +/// let it = [2, 3, 4].iter(); +/// let seq = SequenceOf::from_iter(it); +/// +/// // `seq` now contains the serialized DER representation of the array +/// +/// // iterate objects +/// let mut sum = 0; +/// for item in seq.iter() { +/// // item has type `Result<u32>`, since parsing the serialized bytes could fail +/// sum += *item; +/// } +/// assert_eq!(sum, 9); +/// +/// ``` +#[derive(Debug)] +pub struct SequenceOf<T> { + pub(crate) items: Vec<T>, +} + +impl<T> SequenceOf<T> { + /// Builds a `SEQUENCE OF` from the provided content + #[inline] + pub const fn new(items: Vec<T>) -> Self { + SequenceOf { items } + } + + /// Returns the length of this `SEQUENCE` (the number of items). + #[inline] + pub fn len(&self) -> usize { + self.items.len() + } + + /// Returns `true` if this `SEQUENCE` is empty. + #[inline] + pub fn is_empty(&self) -> bool { + self.items.is_empty() + } + + /// Returns an iterator over the items of the `SEQUENCE`. + #[inline] + pub fn iter(&self) -> impl Iterator<Item = &T> { + self.items.iter() + } +} + +impl<T> AsRef<[T]> for SequenceOf<T> { + fn as_ref(&self) -> &[T] { + &self.items + } +} + +impl<'a, T> IntoIterator for &'a SequenceOf<T> { + type Item = &'a T; + type IntoIter = core::slice::Iter<'a, T>; + + fn into_iter(self) -> core::slice::Iter<'a, T> { + self.items.iter() + } +} + +impl<'a, T> IntoIterator for &'a mut SequenceOf<T> { + type Item = &'a mut T; + type IntoIter = core::slice::IterMut<'a, T>; + + fn into_iter(self) -> core::slice::IterMut<'a, T> { + self.items.iter_mut() + } +} + +impl<T> From<SequenceOf<T>> for Vec<T> { + fn from(set: SequenceOf<T>) -> Self { + set.items + } +} + +impl<T> FromIterator<T> for SequenceOf<T> { + fn from_iter<IT: IntoIterator<Item = T>>(iter: IT) -> Self { + let items = iter.into_iter().collect(); + SequenceOf::new(items) + } +} + +impl<'a, T> TryFrom<Any<'a>> for SequenceOf<T> +where + T: FromBer<'a>, +{ + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<Self> { + any.tag().assert_eq(Self::TAG)?; + if !any.header.is_constructed() { + return Err(Error::ConstructExpected); + } + let items = SequenceIterator::<T, BerParser>::new(any.data).collect::<Result<Vec<T>>>()?; + Ok(SequenceOf::new(items)) + } +} + +impl<T> CheckDerConstraints for SequenceOf<T> +where + T: CheckDerConstraints, +{ + fn check_constraints(any: &Any) -> Result<()> { + any.tag().assert_eq(Self::TAG)?; + any.header.assert_constructed()?; + for item in SequenceIterator::<Any, DerParser>::new(any.data) { + let item = item?; + T::check_constraints(&item)?; + } + Ok(()) + } +} + +impl<T> DerAutoDerive for SequenceOf<T> {} + +impl<T> Tagged for SequenceOf<T> { + const TAG: Tag = Tag::Sequence; +} + +#[cfg(feature = "std")] +impl<T> ToDer for SequenceOf<T> +where + T: ToDer, +{ + fn to_der_len(&self) -> Result<usize> { + self.items.to_der_len() + } + + fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + self.items.write_der_header(writer) + } + + fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + self.items.write_der_content(writer) + } +} diff --git a/src/asn1_types/sequence/vec.rs b/src/asn1_types/sequence/vec.rs new file mode 100644 index 0000000..d8beead --- /dev/null +++ b/src/asn1_types/sequence/vec.rs @@ -0,0 +1,138 @@ +use crate::*; +use alloc::vec::Vec; +use core::convert::TryFrom; + +// // XXX this compiles but requires bound TryFrom :/ +// impl<'a, 'b, T> TryFrom<&'b Any<'a>> for Vec<T> +// where +// T: TryFrom<&'b Any<'a>>, +// for<'e> <T as TryFrom<&'b Any<'a>>>::Error: From<Error>, +// T: FromBer<'a, <T as TryFrom<&'b Any<'a>>>::Error>, +// // T: FromBer<'a, E>, +// // E: From<Error>, +// { +// type Error = <T as TryFrom<&'b Any<'a>>>::Error; + +// fn try_from(any: &'b Any<'a>) -> Result<Vec<T>, Self::Error> { +// any.tag().assert_eq(Self::TAG)?; +// any.header.assert_constructed()?; +// let v = SequenceIterator::<T, BerParser, Self::Error>::new(any.data) +// .collect::<Result<Vec<T>, Self::Error>>()?; +// Ok(v) +// } +// } + +// // XXX this compiles but requires bound TryFrom :/ +// impl<'a, 'b, T> TryFrom<&'b Any<'a>> for Vec<T> +// where +// T: TryFrom<&'b Any<'a>>, +// <T as TryFrom<&'b Any<'a>>>::Error: From<Error>, +// T: FromBer<'a, <T as TryFrom<&'b Any<'a>>>::Error>, +// // T: FromBer<'a, E>, +// // E: From<Error>, +// { +// type Error = <T as TryFrom<&'b Any<'a>>>::Error; + +// fn try_from(any: &'b Any<'a>) -> Result<Vec<T>, Self::Error> { +// any.tag().assert_eq(Self::TAG)?; +// any.header.assert_constructed()?; +// let v = SequenceIterator::<T, BerParser, Self::Error>::new(any.data) +// .collect::<Result<Vec<T>, Self::Error>>()?; +// Ok(v) +// } +// } + +impl<'a, T> TryFrom<Any<'a>> for Vec<T> +where + T: FromBer<'a>, +{ + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<Self> { + any.tag().assert_eq(Self::TAG)?; + any.header.assert_constructed()?; + let items = SetIterator::<T, BerParser>::new(any.data).collect::<Result<Vec<T>>>()?; + Ok(items) + } +} + +impl<T> CheckDerConstraints for Vec<T> +where + T: CheckDerConstraints, +{ + fn check_constraints(any: &Any) -> Result<()> { + any.tag().assert_eq(Self::TAG)?; + any.header.assert_constructed()?; + for item in SequenceIterator::<Any, DerParser>::new(any.data) { + let item = item?; + <T as CheckDerConstraints>::check_constraints(&item)?; + } + Ok(()) + } +} + +impl<T> Tagged for Vec<T> { + const TAG: Tag = Tag::Sequence; +} + +// impl<'a, T> FromBer<'a> for Vec<T> +// where +// T: FromBer<'a>, +// { +// fn from_ber(bytes: &'a [u8]) -> ParseResult<Self> { +// let (rem, any) = Any::from_ber(bytes)?; +// any.header.assert_tag(Self::TAG)?; +// let v = SequenceIterator::<T, BerParser>::new(any.data).collect::<Result<Vec<T>>>()?; +// Ok((rem, v)) +// } +// } + +/// manual impl of FromDer, so we do not need to require `TryFrom<Any> + CheckDerConstraints` +impl<'a, T, E> FromDer<'a, E> for Vec<T> +where + T: FromDer<'a, E>, + E: From<Error>, +{ + fn from_der(bytes: &'a [u8]) -> ParseResult<Self, E> { + let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?; + any.header + .assert_tag(Self::TAG) + .map_err(|e| nom::Err::Error(e.into()))?; + let v = SequenceIterator::<T, DerParser, E>::new(any.data) + .collect::<Result<Vec<T>, E>>() + .map_err(nom::Err::Error)?; + Ok((rem, v)) + } +} + +#[cfg(feature = "std")] +impl<T> ToDer for Vec<T> +where + T: ToDer, +{ + fn to_der_len(&self) -> Result<usize> { + let mut len = 0; + for t in self.iter() { + len += t.to_der_len()?; + } + let header = Header::new(Class::Universal, true, Self::TAG, Length::Definite(len)); + Ok(header.to_der_len()? + len) + } + + fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let mut len = 0; + for t in self.iter() { + len += t.to_der_len().map_err(|_| SerializeError::InvalidLength)?; + } + let header = Header::new(Class::Universal, true, Self::TAG, Length::Definite(len)); + header.write_der_header(writer).map_err(Into::into) + } + + fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let mut sz = 0; + for t in self.iter() { + sz += t.write_der(writer)?; + } + Ok(sz) + } +} diff --git a/src/asn1_types/set.rs b/src/asn1_types/set.rs new file mode 100644 index 0000000..ef693d2 --- /dev/null +++ b/src/asn1_types/set.rs @@ -0,0 +1,387 @@ +use crate::*; +use alloc::borrow::Cow; +use alloc::vec::Vec; +use core::convert::TryFrom; + +mod btreeset; +mod hashset; +mod iterator; +mod set_of; + +pub use btreeset::*; +#[cfg(feature = "std")] +pub use hashset::*; +pub use iterator::*; +pub use set_of::*; + +/// The `SET` object is an unordered list of heteregeneous types. +/// +/// Sets can usually be of 2 types: +/// - a list of different objects (`SET`, usually parsed as a `struct`) +/// - a list of similar objects (`SET OF`, usually parsed as a `BTreeSet<T>` or `HashSet<T>`) +/// +/// The current object covers the former. For the latter, see the [`SetOf`] documentation. +/// +/// The `Set` object contains the (*unparsed*) encoded representation of its content. It provides +/// methods to parse and iterate contained objects, or convert the sequence to other types. +/// +/// # Building a Set +/// +/// To build a DER set: +/// - if the set is composed of objects of the same type, the [`Set::from_iter_to_der`] method can be used +/// - otherwise, the [`ToDer`] trait can be used to create content incrementally +/// +/// ``` +/// use asn1_rs::{Integer, Set, SerializeResult, ToDer}; +/// +/// fn build_set<'a>() -> SerializeResult<Set<'a>> { +/// let mut v = Vec::new(); +/// // add an Integer object (construct type): +/// let i = Integer::from_u32(4); +/// let _ = i.write_der(&mut v)?; +/// // some primitive objects also implement `ToDer`. A string will be mapped as `Utf8String`: +/// let _ = "abcd".write_der(&mut v)?; +/// // return the set built from the DER content +/// Ok(Set::new(v.into())) +/// } +/// +/// let seq = build_set().unwrap(); +/// +/// ``` +/// +/// # Examples +/// +/// ``` +/// use asn1_rs::{Error, Set}; +/// +/// // build set +/// let it = [2, 3, 4].iter(); +/// let set = Set::from_iter_to_der(it).unwrap(); +/// +/// // `set` now contains the serialized DER representation of the array +/// +/// // iterate objects +/// let mut sum = 0; +/// for item in set.der_iter::<u32, Error>() { +/// // item has type `Result<u32>`, since parsing the serialized bytes could fail +/// sum += item.expect("parsing list item failed"); +/// } +/// assert_eq!(sum, 9); +/// +/// ``` +/// +/// Note: the above example encodes a `SET OF INTEGER` object, the [`SetOf`] object could +/// be used to provide a simpler API. +/// +#[derive(Clone, Debug)] +pub struct Set<'a> { + /// Serialized DER representation of the set content + pub content: Cow<'a, [u8]>, +} + +impl<'a> Set<'a> { + /// Build a set, given the provided content + pub const fn new(content: Cow<'a, [u8]>) -> Self { + Set { content } + } + + /// Consume the set and return the content + #[inline] + pub fn into_content(self) -> Cow<'a, [u8]> { + self.content + } + + /// Apply the parsing function to the set content, consuming the set + /// + /// Note: this function expects the caller to take ownership of content. + /// In some cases, handling the lifetime of objects is not easy (when keeping only references on + /// data). Other methods are provided (depending on the use case): + /// - [`Set::parse`] takes a reference on the set data, but does not consume it, + /// - [`Set::from_der_and_then`] does the parsing of the set and applying the function + /// in one step, ensuring there are only references (and dropping the temporary set). + pub fn and_then<U, F, E>(self, op: F) -> ParseResult<'a, U, E> + where + F: FnOnce(Cow<'a, [u8]>) -> ParseResult<U, E>, + { + op(self.content) + } + + /// Same as [`Set::from_der_and_then`], but using BER encoding (no constraints). + pub fn from_ber_and_then<U, F, E>(bytes: &'a [u8], op: F) -> ParseResult<'a, U, E> + where + F: FnOnce(&'a [u8]) -> ParseResult<U, E>, + E: From<Error>, + { + let (rem, seq) = Set::from_ber(bytes).map_err(Err::convert)?; + let data = match seq.content { + Cow::Borrowed(b) => b, + // Since 'any' is built from 'bytes', it is borrowed by construction + Cow::Owned(_) => unreachable!(), + }; + let (_, res) = op(data)?; + Ok((rem, res)) + } + + /// Parse a DER set and apply the provided parsing function to content + /// + /// After parsing, the set object and header are discarded. + /// + /// ``` + /// use asn1_rs::{FromDer, ParseResult, Set}; + /// + /// // Parse a SET { + /// // a INTEGER (0..255), + /// // b INTEGER (0..4294967296) + /// // } + /// // and return only `(a,b) + /// fn parser(i: &[u8]) -> ParseResult<(u8, u32)> { + /// Set::from_der_and_then(i, |i| { + /// let (i, a) = u8::from_der(i)?; + /// let (i, b) = u32::from_der(i)?; + /// Ok((i, (a, b))) + /// } + /// ) + /// } + /// ``` + pub fn from_der_and_then<U, F, E>(bytes: &'a [u8], op: F) -> ParseResult<'a, U, E> + where + F: FnOnce(&'a [u8]) -> ParseResult<U, E>, + E: From<Error>, + { + let (rem, seq) = Set::from_der(bytes).map_err(Err::convert)?; + let data = match seq.content { + Cow::Borrowed(b) => b, + // Since 'any' is built from 'bytes', it is borrowed by construction + Cow::Owned(_) => unreachable!(), + }; + let (_, res) = op(data)?; + Ok((rem, res)) + } + + /// Apply the parsing function to the set content (non-consuming version) + pub fn parse<F, T, E>(&'a self, mut f: F) -> ParseResult<'a, T, E> + where + F: FnMut(&'a [u8]) -> ParseResult<'a, T, E>, + { + let input: &[u8] = &self.content; + f(input) + } + + /// Apply the parsing function to the set content (consuming version) + /// + /// Note: to parse and apply a parsing function in one step, use the + /// [`Set::from_der_and_then`] method. + /// + /// # Limitations + /// + /// This function fails if the set contains `Owned` data, because the parsing function + /// takes a reference on data (which is dropped). + pub fn parse_into<F, T, E>(self, mut f: F) -> ParseResult<'a, T, E> + where + F: FnMut(&'a [u8]) -> ParseResult<'a, T, E>, + E: From<Error>, + { + match self.content { + Cow::Borrowed(b) => f(b), + _ => Err(nom::Err::Error(Error::LifetimeError.into())), + } + } + + /// Return an iterator over the set content, attempting to decode objects as BER + /// + /// This method can be used when all objects from the set have the same type. + pub fn ber_iter<T, E>(&'a self) -> SetIterator<'a, T, BerParser, E> + where + T: FromBer<'a, E>, + { + SetIterator::new(&self.content) + } + + /// Return an iterator over the set content, attempting to decode objects as DER + /// + /// This method can be used when all objects from the set have the same type. + pub fn der_iter<T, E>(&'a self) -> SetIterator<'a, T, DerParser, E> + where + T: FromDer<'a, E>, + { + SetIterator::new(&self.content) + } + + /// Attempt to parse the set as a `SET OF` items (BER), and return the parsed items as a `Vec`. + pub fn ber_set_of<T, E>(&'a self) -> Result<Vec<T>, E> + where + T: FromBer<'a, E>, + E: From<Error>, + { + self.ber_iter().collect() + } + + /// Attempt to parse the set as a `SET OF` items (DER), and return the parsed items as a `Vec`. + pub fn der_set_of<T, E>(&'a self) -> Result<Vec<T>, E> + where + T: FromDer<'a, E>, + E: From<Error>, + { + self.der_iter().collect() + } + + /// Attempt to parse the set as a `SET OF` items (BER) (consuming input), + /// and return the parsed items as a `Vec`. + /// + /// Note: if `Self` is an `Owned` object, the data will be duplicated (causing allocations) into separate objects. + pub fn into_ber_set_of<T, E>(self) -> Result<Vec<T>, E> + where + for<'b> T: FromBer<'b, E>, + E: From<Error>, + T: ToStatic<Owned = T>, + { + match self.content { + Cow::Borrowed(bytes) => SetIterator::<T, BerParser, E>::new(bytes).collect(), + Cow::Owned(data) => { + let v1 = + SetIterator::<T, BerParser, E>::new(&data).collect::<Result<Vec<T>, E>>()?; + let v2 = v1.iter().map(|t| t.to_static()).collect::<Vec<_>>(); + Ok(v2) + } + } + } + + /// Attempt to parse the set as a `SET OF` items (DER) (consuming input), + /// and return the parsed items as a `Vec`. + /// + /// Note: if `Self` is an `Owned` object, the data will be duplicated (causing allocations) into separate objects. + pub fn into_der_set_of<T, E>(self) -> Result<Vec<T>, E> + where + for<'b> T: FromDer<'b, E>, + E: From<Error>, + T: ToStatic<Owned = T>, + { + match self.content { + Cow::Borrowed(bytes) => SetIterator::<T, DerParser, E>::new(bytes).collect(), + Cow::Owned(data) => { + let v1 = + SetIterator::<T, DerParser, E>::new(&data).collect::<Result<Vec<T>, E>>()?; + let v2 = v1.iter().map(|t| t.to_static()).collect::<Vec<_>>(); + Ok(v2) + } + } + } + + pub fn into_der_set_of_ref<T, E>(self) -> Result<Vec<T>, E> + where + T: FromDer<'a, E>, + E: From<Error>, + { + match self.content { + Cow::Borrowed(bytes) => SetIterator::<T, DerParser, E>::new(bytes).collect(), + Cow::Owned(_) => Err(Error::LifetimeError.into()), + } + } +} + +impl<'a> ToStatic for Set<'a> { + type Owned = Set<'static>; + + fn to_static(&self) -> Self::Owned { + Set { + content: Cow::Owned(self.content.to_vec()), + } + } +} + +impl<'a> AsRef<[u8]> for Set<'a> { + fn as_ref(&self) -> &[u8] { + &self.content + } +} + +impl<'a> TryFrom<Any<'a>> for Set<'a> { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<Set<'a>> { + TryFrom::try_from(&any) + } +} + +impl<'a, 'b> TryFrom<&'b Any<'a>> for Set<'a> { + type Error = Error; + + fn try_from(any: &'b Any<'a>) -> Result<Set<'a>> { + any.tag().assert_eq(Self::TAG)?; + any.header.assert_constructed()?; + Ok(Set { + content: Cow::Borrowed(any.data), + }) + } +} + +impl<'a> CheckDerConstraints for Set<'a> { + fn check_constraints(_any: &Any) -> Result<()> { + Ok(()) + } +} + +impl<'a> DerAutoDerive for Set<'a> {} + +impl<'a> Tagged for Set<'a> { + const TAG: Tag = Tag::Set; +} + +#[cfg(feature = "std")] +impl ToDer for Set<'_> { + fn to_der_len(&self) -> Result<usize> { + let sz = self.content.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, + true, + Self::TAG, + Length::Definite(self.content.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.content).map_err(Into::into) + } +} + +#[cfg(feature = "std")] +impl<'a> Set<'a> { + /// Attempt to create a `Set` from an iterator over serializable objects (to DER) + /// + /// # Examples + /// + /// ``` + /// use asn1_rs::Set; + /// + /// // build set + /// let it = [2, 3, 4].iter(); + /// let seq = Set::from_iter_to_der(it).unwrap(); + /// ``` + pub fn from_iter_to_der<T, IT>(it: IT) -> SerializeResult<Self> + where + IT: Iterator<Item = T>, + T: ToDer, + T: Tagged, + { + let mut v = Vec::new(); + for item in it { + let item_v = <T as ToDer>::to_der_vec(&item)?; + v.extend_from_slice(&item_v); + } + Ok(Set { + content: Cow::Owned(v), + }) + } +} diff --git a/src/asn1_types/set/btreeset.rs b/src/asn1_types/set/btreeset.rs new file mode 100644 index 0000000..a70f9f9 --- /dev/null +++ b/src/asn1_types/set/btreeset.rs @@ -0,0 +1,124 @@ +use crate::*; +use alloc::collections::BTreeSet; +use core::convert::TryFrom; + +impl<T> Tagged for BTreeSet<T> { + const TAG: Tag = Tag::Set; +} + +impl<'a, T> TryFrom<Any<'a>> for BTreeSet<T> +where + T: FromBer<'a>, + T: Ord, +{ + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<Self> { + any.tag().assert_eq(Self::TAG)?; + any.header.assert_constructed()?; + let items = SetIterator::<T, BerParser>::new(any.data).collect::<Result<BTreeSet<T>>>()?; + Ok(items) + } +} + +impl<T> CheckDerConstraints for BTreeSet<T> +where + T: CheckDerConstraints, +{ + fn check_constraints(any: &Any) -> Result<()> { + any.tag().assert_eq(Self::TAG)?; + any.header.assert_constructed()?; + for item in SetIterator::<Any, DerParser>::new(any.data) { + let item = item?; + T::check_constraints(&item)?; + } + Ok(()) + } +} + +/// manual impl of FromDer, so we do not need to require `TryFrom<Any> + CheckDerConstraints` +impl<'a, T, E> FromDer<'a, E> for BTreeSet<T> +where + T: FromDer<'a, E>, + T: Ord, + E: From<Error>, +{ + fn from_der(bytes: &'a [u8]) -> ParseResult<'a, Self, E> { + let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?; + any.tag() + .assert_eq(Self::TAG) + .map_err(|e| nom::Err::Error(e.into()))?; + any.header + .assert_constructed() + .map_err(|e| nom::Err::Error(e.into()))?; + let items = SetIterator::<T, DerParser, E>::new(any.data) + .collect::<Result<BTreeSet<T>, E>>() + .map_err(nom::Err::Error)?; + Ok((rem, items)) + } +} + +#[cfg(feature = "std")] +impl<T> ToDer for BTreeSet<T> +where + T: ToDer, +{ + fn to_der_len(&self) -> Result<usize> { + let mut len = 0; + for t in self.iter() { + len += t.to_der_len()?; + } + let header = Header::new(Class::Universal, true, Self::TAG, Length::Definite(len)); + Ok(header.to_der_len()? + len) + } + + fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let mut len = 0; + for t in self.iter() { + len += t.to_der_len().map_err(|_| SerializeError::InvalidLength)?; + } + let header = Header::new(Class::Universal, true, Self::TAG, Length::Definite(len)); + header.write_der_header(writer).map_err(Into::into) + } + + fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let mut sz = 0; + for t in self.iter() { + sz += t.write_der(writer)?; + } + Ok(sz) + } +} + +#[cfg(test)] +mod tests { + use crate::*; + use core::convert::TryFrom; + use hex_literal::hex; + use std::collections::BTreeSet; + + #[test] + fn ber_btreeset() { + let input = &hex! {"31 06 02 01 00 02 01 01"}; + let (_, any) = Any::from_ber(input).expect("parsing hashset failed"); + <BTreeSet<u32>>::check_constraints(&any).unwrap(); + + let h = <BTreeSet<u32>>::try_from(any).unwrap(); + + assert_eq!(h.len(), 2); + } + + #[test] + fn der_btreeset() { + let input = &hex! {"31 06 02 01 00 02 01 01"}; + let r: IResult<_, _, Error> = BTreeSet::<u32>::from_der(input); + let (_, h) = r.expect("parsing hashset failed"); + + assert_eq!(h.len(), 2); + + assert_eq!(h.to_der_len(), Ok(8)); + let v = h.to_der_vec().expect("could not serialize"); + let (_, h2) = SetOf::<u32>::from_der(&v).unwrap(); + assert!(h.iter().eq(h2.iter())); + } +} diff --git a/src/asn1_types/set/hashset.rs b/src/asn1_types/set/hashset.rs new file mode 100644 index 0000000..d505301 --- /dev/null +++ b/src/asn1_types/set/hashset.rs @@ -0,0 +1,125 @@ +#![cfg(feature = "std")] +use crate::*; +use std::collections::HashSet; +use std::convert::TryFrom; +use std::hash::Hash; + +impl<T> Tagged for HashSet<T> { + const TAG: Tag = Tag::Set; +} + +impl<'a, T> TryFrom<Any<'a>> for HashSet<T> +where + T: FromBer<'a>, + T: Hash + Eq, +{ + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<Self> { + any.tag().assert_eq(Self::TAG)?; + any.header.assert_constructed()?; + let items = SetIterator::<T, BerParser>::new(any.data).collect::<Result<HashSet<T>>>()?; + Ok(items) + } +} + +impl<T> CheckDerConstraints for HashSet<T> +where + T: CheckDerConstraints, +{ + fn check_constraints(any: &Any) -> Result<()> { + any.tag().assert_eq(Self::TAG)?; + any.header.assert_constructed()?; + for item in SetIterator::<Any, DerParser>::new(any.data) { + let item = item?; + T::check_constraints(&item)?; + } + Ok(()) + } +} + +/// manual impl of FromDer, so we do not need to require `TryFrom<Any> + CheckDerConstraints` +impl<'a, T, E> FromDer<'a, E> for HashSet<T> +where + T: FromDer<'a, E>, + T: Hash + Eq, + E: From<Error>, +{ + fn from_der(bytes: &'a [u8]) -> ParseResult<'a, Self, E> { + let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?; + any.tag() + .assert_eq(Self::TAG) + .map_err(|e| nom::Err::Error(e.into()))?; + any.header + .assert_constructed() + .map_err(|e| nom::Err::Error(e.into()))?; + let items = SetIterator::<T, DerParser, E>::new(any.data) + .collect::<Result<HashSet<T>, E>>() + .map_err(nom::Err::Error)?; + Ok((rem, items)) + } +} + +impl<T> ToDer for HashSet<T> +where + T: ToDer, +{ + fn to_der_len(&self) -> Result<usize> { + let mut len = 0; + for t in self.iter() { + len += t.to_der_len()?; + } + let header = Header::new(Class::Universal, true, Self::TAG, Length::Definite(len)); + Ok(header.to_der_len()? + len) + } + + fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let mut len = 0; + for t in self.iter() { + len += t.to_der_len().map_err(|_| SerializeError::InvalidLength)?; + } + let header = Header::new(Class::Universal, true, Self::TAG, Length::Definite(len)); + header.write_der_header(writer).map_err(Into::into) + } + + fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let mut sz = 0; + for t in self.iter() { + sz += t.write_der(writer)?; + } + Ok(sz) + } +} + +#[cfg(test)] +mod tests { + use crate::*; + use core::convert::TryFrom; + use hex_literal::hex; + use std::collections::HashSet; + + #[test] + fn ber_hashset() { + let input = &hex! {"31 06 02 01 00 02 01 01"}; + let (_, any) = Any::from_ber(input).expect("parsing hashset failed"); + <HashSet<u32>>::check_constraints(&any).unwrap(); + + let h = <HashSet<u32>>::try_from(any).unwrap(); + + assert_eq!(h.len(), 2); + } + + #[test] + fn der_hashset() { + let input = &hex! {"31 06 02 01 00 02 01 01"}; + let r: IResult<_, _, Error> = HashSet::<u32>::from_der(input); + let (_, h) = r.expect("parsing hashset failed"); + + assert_eq!(h.len(), 2); + + assert_eq!(h.to_der_len(), Ok(8)); + let v = h.to_der_vec().expect("could not serialize"); + let (_, h2) = SetOf::<u32>::from_der(&v).unwrap(); + assert!(h.iter().eq(h2.iter())); + } +} diff --git a/src/asn1_types/set/iterator.rs b/src/asn1_types/set/iterator.rs new file mode 100644 index 0000000..eb46075 --- /dev/null +++ b/src/asn1_types/set/iterator.rs @@ -0,0 +1,22 @@ +pub use crate::{Error, SequenceIterator}; + +/// An Iterator over binary data, parsing elements of type `T` +/// +/// This helps parsing `SET OF` items of type `T`. The type of parser +/// (BER/DER) is specified using the generic parameter `F` of this struct. +/// +/// Note: the iterator must start on the set *contents*, not the set itself. +/// +/// # Examples +/// +/// ```rust +/// use asn1_rs::{DerParser, Integer, SetIterator}; +/// +/// let data = &[0x30, 0x6, 0x2, 0x1, 0x1, 0x2, 0x1, 0x2]; +/// for (idx, item) in SetIterator::<Integer, DerParser>::new(&data[2..]).enumerate() { +/// let item = item.unwrap(); // parsing could have failed +/// let i = item.as_u32().unwrap(); // integer can be negative, or too large to fit into u32 +/// assert_eq!(i as usize, idx + 1); +/// } +/// ``` +pub type SetIterator<'a, T, F, E = Error> = SequenceIterator<'a, T, F, E>; diff --git a/src/asn1_types/set/set_of.rs b/src/asn1_types/set/set_of.rs new file mode 100644 index 0000000..af6c0a7 --- /dev/null +++ b/src/asn1_types/set/set_of.rs @@ -0,0 +1,150 @@ +use crate::*; +use alloc::vec::Vec; +use core::convert::TryFrom; +use core::iter::FromIterator; + +/// The `SET OF` object is an unordered list of homogeneous types. +/// +/// # Examples +/// +/// ``` +/// use asn1_rs::SetOf; +/// use std::iter::FromIterator; +/// +/// // build set +/// let it = [2, 3, 4].iter(); +/// let set = SetOf::from_iter(it); +/// +/// // `set` now contains the serialized DER representation of the array +/// +/// // iterate objects +/// let mut sum = 0; +/// for item in set.iter() { +/// // item has type `Result<u32>`, since parsing the serialized bytes could fail +/// sum += *item; +/// } +/// assert_eq!(sum, 9); +/// +/// ``` +#[derive(Debug)] +pub struct SetOf<T> { + items: Vec<T>, +} + +impl<T> SetOf<T> { + /// Builds a `SET OF` from the provided content + #[inline] + pub const fn new(items: Vec<T>) -> Self { + SetOf { items } + } + + /// Returns the length of this `SET` (the number of items). + #[inline] + pub fn len(&self) -> usize { + self.items.len() + } + + /// Returns `true` if this `SET` is empty. + #[inline] + pub fn is_empty(&self) -> bool { + self.items.is_empty() + } + + /// Returns an iterator over the items of the `SET`. + #[inline] + pub fn iter(&self) -> impl Iterator<Item = &T> { + self.items.iter() + } +} + +impl<T> AsRef<[T]> for SetOf<T> { + fn as_ref(&self) -> &[T] { + &self.items + } +} + +impl<'a, T> IntoIterator for &'a SetOf<T> { + type Item = &'a T; + type IntoIter = core::slice::Iter<'a, T>; + + fn into_iter(self) -> core::slice::Iter<'a, T> { + self.items.iter() + } +} + +impl<'a, T> IntoIterator for &'a mut SetOf<T> { + type Item = &'a mut T; + type IntoIter = core::slice::IterMut<'a, T>; + + fn into_iter(self) -> core::slice::IterMut<'a, T> { + self.items.iter_mut() + } +} + +impl<T> From<SetOf<T>> for Vec<T> { + fn from(set: SetOf<T>) -> Self { + set.items + } +} + +impl<T> FromIterator<T> for SetOf<T> { + fn from_iter<IT: IntoIterator<Item = T>>(iter: IT) -> Self { + let items = iter.into_iter().collect(); + SetOf::new(items) + } +} + +impl<'a, T> TryFrom<Any<'a>> for SetOf<T> +where + T: FromBer<'a>, +{ + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<Self> { + any.tag().assert_eq(Self::TAG)?; + if !any.header.is_constructed() { + return Err(Error::ConstructExpected); + } + let items = SetIterator::<T, BerParser>::new(any.data).collect::<Result<Vec<T>>>()?; + Ok(SetOf::new(items)) + } +} + +impl<T> CheckDerConstraints for SetOf<T> +where + T: CheckDerConstraints, +{ + fn check_constraints(any: &Any) -> Result<()> { + any.tag().assert_eq(Self::TAG)?; + any.header.assert_constructed()?; + for item in SetIterator::<Any, DerParser>::new(any.data) { + let item = item?; + T::check_constraints(&item)?; + } + Ok(()) + } +} + +impl<T> DerAutoDerive for SetOf<T> {} + +impl<T> Tagged for SetOf<T> { + const TAG: Tag = Tag::Set; +} + +#[cfg(feature = "std")] +impl<T> ToDer for SetOf<T> +where + T: ToDer, +{ + fn to_der_len(&self) -> Result<usize> { + self.items.to_der_len() + } + + fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + self.items.write_der_header(writer) + } + + fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + self.items.write_der_content(writer) + } +} diff --git a/src/asn1_types/strings.rs b/src/asn1_types/strings.rs new file mode 100644 index 0000000..66a4c19 --- /dev/null +++ b/src/asn1_types/strings.rs @@ -0,0 +1,171 @@ +mod bmpstring; +mod generalstring; +mod graphicstring; +mod ia5string; +mod numericstring; +mod printablestring; +mod str; +mod string; +mod teletexstring; +mod universalstring; +mod utf8string; +mod videotexstring; +mod visiblestring; + +pub use self::str::*; +pub use bmpstring::*; +pub use generalstring::*; +pub use graphicstring::*; +pub use ia5string::*; +pub use numericstring::*; +pub use printablestring::*; +pub use string::*; +pub use teletexstring::*; +pub use universalstring::*; +pub use utf8string::*; +pub use videotexstring::*; +pub use visiblestring::*; + +/// Base trait for BER string objects and character set validation +/// +/// This trait is implemented by several types, and is used to determine if some bytes +/// would be valid for the given type. +/// +/// # Example +/// +/// ```rust +/// use asn1_rs::{PrintableString, TestValidCharset, VisibleString}; +/// +/// let bytes: &[u8] = b"abcd*4"; +/// let res = PrintableString::test_valid_charset(bytes); +/// assert!(res.is_err()); +/// let res = VisibleString::test_valid_charset(bytes); +/// assert!(res.is_ok()); +/// ``` +pub trait TestValidCharset { + /// Check character set for this object type. + fn test_valid_charset(i: &[u8]) -> crate::Result<()>; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! asn1_string { + (IMPL $name:ident, $sname:expr) => { + #[doc="ASN.1 restricted character string type (`"] + #[doc = $sname] + #[doc = "`)"] + #[derive(Debug, PartialEq, Eq)] + pub struct $name<'a> { + pub(crate) data: alloc::borrow::Cow<'a, str>, + } + + impl<'a> $name<'a> { + pub const fn new(s: &'a str) -> Self { + $name { + data: alloc::borrow::Cow::Borrowed(s), + } + } + + pub fn string(&self) -> String { + use alloc::string::ToString; + self.data.to_string() + } + } + + impl<'a> AsRef<str> for $name<'a> { + fn as_ref(&self) -> &str { + &self.data + } + } + + impl<'a> From<&'a str> for $name<'a> { + fn from(s: &'a str) -> Self { + Self::new(s) + } + } + + impl From<String> for $name<'_> { + fn from(s: String) -> Self { + Self { + data: alloc::borrow::Cow::Owned(s), + } + } + } + + impl<'a> core::convert::TryFrom<$crate::Any<'a>> for $name<'a> { + type Error = $crate::Error; + + fn try_from(any: $crate::Any<'a>) -> $crate::Result<$name<'a>> { + use core::convert::TryFrom; + TryFrom::try_from(&any) + } + } + + impl<'a, 'b> core::convert::TryFrom<&'b $crate::Any<'a>> for $name<'a> { + type Error = $crate::Error; + + fn try_from(any: &'b $crate::Any<'a>) -> $crate::Result<$name<'a>> { + use $crate::traits::Tagged; + use alloc::borrow::Cow; + any.tag().assert_eq(Self::TAG)?; + <$name>::test_valid_charset(any.data)?; + + let s = alloc::str::from_utf8(any.data)?; + let data = Cow::Borrowed(s); + Ok($name { data }) + } + } + + impl<'a> $crate::CheckDerConstraints for $name<'a> { + fn check_constraints(any: &$crate::Any) -> $crate::Result<()> { + any.header.assert_primitive()?; + Ok(()) + } + } + + impl $crate::DerAutoDerive for $name<'_> {} + + impl<'a> $crate::Tagged for $name<'a> { + const TAG: $crate::Tag = $crate::Tag::$name; + } + + #[cfg(feature = "std")] + impl $crate::ToDer for $name<'_> { + fn to_der_len(&self) -> Result<usize> { + let sz = self.data.as_bytes().len(); + if sz < 127 { + // 1 (class+tag) + 1 (length) + len + Ok(2 + sz) + } else { + // 1 (class+tag) + n (length) + len + let n = $crate::Length::Definite(sz).to_der_len()?; + Ok(1 + n + sz) + } + } + + fn write_der_header( + &self, + writer: &mut dyn std::io::Write, + ) -> $crate::SerializeResult<usize> { + use $crate::Tagged; + let header = $crate::Header::new( + $crate::Class::Universal, + false, + Self::TAG, + $crate::Length::Definite(self.data.len()), + ); + header.write_der_header(writer).map_err(Into::into) + } + + fn write_der_content( + &self, + writer: &mut dyn std::io::Write, + ) -> $crate::SerializeResult<usize> { + writer.write(self.data.as_bytes()).map_err(Into::into) + } + } + }; + ($name:ident) => { + asn1_string!(IMPL $name, stringify!($name)); + }; +} 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(()) + } +} diff --git a/src/asn1_types/tagged.rs b/src/asn1_types/tagged.rs new file mode 100644 index 0000000..c1652d4 --- /dev/null +++ b/src/asn1_types/tagged.rs @@ -0,0 +1,128 @@ +use crate::{Class, Error, Tag, Tagged}; +use core::marker::PhantomData; + +mod application; +mod builder; +mod explicit; +mod helpers; +mod implicit; +mod optional; +mod parser; +mod private; + +pub use application::*; +pub use builder::*; +pub use explicit::*; +pub use helpers::*; +pub use implicit::*; +pub use optional::*; +pub use parser::*; +pub use private::*; + +pub(crate) const CONTEXT_SPECIFIC: u8 = Class::ContextSpecific as u8; + +/// A type parameter for `IMPLICIT` tagged values. +#[derive(Debug, PartialEq, Eq)] +pub enum Implicit {} + +/// A type parameter for `EXPLICIT` tagged values. +#[derive(Debug, PartialEq, Eq)] +pub enum Explicit {} + +/// A type parameter for tagged values either [`Explicit`] or [`Implicit`]. +pub trait TagKind {} + +impl TagKind for Implicit {} +impl TagKind for Explicit {} + +/// Helper object for creating `FromBer`/`FromDer` types for TAGGED OPTIONAL types +/// +/// When parsing `ContextSpecific` (the most common class), see [`TaggedExplicit`] and +/// [`TaggedImplicit`] alias types. +/// +/// # Notes +/// +/// `CLASS` must be between 0 and 4. See [`Class`] for possible values for the `CLASS` parameter. +/// Constants from this class can be used, but they must be wrapped in braces due to +/// [Rust syntax for generics](https://doc.rust-lang.org/reference/items/generics.html) +/// (see example below). +/// +/// # Examples +/// +/// To parse a `[APPLICATION 0] EXPLICIT INTEGER` object: +/// +/// ```rust +/// use asn1_rs::{Class, Error, Explicit, FromBer, Integer, TaggedValue}; +/// +/// let bytes = &[0x60, 0x03, 0x2, 0x1, 0x2]; +/// +/// // If tagged object is present (and has expected tag), parsing succeeds: +/// let (_, tagged) = +/// TaggedValue::<Integer, Error, Explicit, {Class::APPLICATION}, 0>::from_ber(bytes) +/// .unwrap(); +/// assert_eq!(tagged, TaggedValue::explicit(Integer::from(2))); +/// ``` +#[derive(Debug, PartialEq, Eq)] +pub struct TaggedValue<T, E, TagKind, const CLASS: u8, const TAG: u32> { + pub(crate) inner: T, + + tag_kind: PhantomData<TagKind>, + _e: PhantomData<E>, +} + +impl<T, E, TagKind, const CLASS: u8, const TAG: u32> TaggedValue<T, E, TagKind, CLASS, TAG> { + /// Consumes the `TaggedParser`, returning the wrapped value. + #[inline] + pub fn into_inner(self) -> T { + self.inner + } + + /// Return the (outer) tag of this object + pub const fn tag(&self) -> Tag { + Self::TAG + } + + /// Return the (outer) class of this object + #[inline] + pub const fn class(&self) -> u8 { + CLASS + } +} + +impl<T, E, const CLASS: u8, const TAG: u32> TaggedValue<T, E, Explicit, CLASS, TAG> { + /// Constructs a new `EXPLICIT TaggedParser` with the provided value + #[inline] + pub const fn explicit(inner: T) -> Self { + TaggedValue { + inner, + tag_kind: PhantomData, + _e: PhantomData, + } + } +} + +impl<T, E, const CLASS: u8, const TAG: u32> TaggedValue<T, E, Implicit, CLASS, TAG> { + /// Constructs a new `IMPLICIT TaggedParser` with the provided value + #[inline] + pub const fn implicit(inner: T) -> Self { + TaggedValue { + inner, + tag_kind: PhantomData, + _e: PhantomData, + } + } +} + +impl<T, E, TagKind, const CLASS: u8, const TAG: u32> AsRef<T> + for TaggedValue<T, E, TagKind, CLASS, TAG> +{ + fn as_ref(&self) -> &T { + &self.inner + } +} + +impl<T, E, TagKind, const CLASS: u8, const TAG: u32> Tagged + for TaggedValue<T, E, TagKind, CLASS, TAG> +{ + const TAG: Tag = Tag(TAG); +} diff --git a/src/asn1_types/tagged/application.rs b/src/asn1_types/tagged/application.rs new file mode 100644 index 0000000..ddd2b07 --- /dev/null +++ b/src/asn1_types/tagged/application.rs @@ -0,0 +1,42 @@ +use crate::{Class, Explicit, Implicit, TaggedValue}; + +/// A helper object to parse `[APPLICATION n] EXPLICIT T` +/// +/// A helper object implementing [`FromBer`](crate::FromBer) and [`FromDer`](crate::FromDer), to +/// parse explicit application-tagged values. +/// +/// # Examples +/// +/// To parse a `[APPLICATION 0] EXPLICIT INTEGER` object: +/// +/// ```rust +/// use asn1_rs::{ApplicationExplicit, Error, FromBer, Integer, TaggedValue}; +/// +/// let bytes = &[0x60, 0x03, 0x2, 0x1, 0x2]; +/// +/// // If tagged object is present (and has expected tag), parsing succeeds: +/// let (_, tagged) = ApplicationExplicit::<Integer, Error, 0>::from_ber(bytes).unwrap(); +/// assert_eq!(tagged, TaggedValue::explicit(Integer::from(2))); +/// ``` +pub type ApplicationExplicit<T, E, const TAG: u32> = + TaggedValue<T, E, Explicit, { Class::APPLICATION }, TAG>; + +/// A helper object to parse `[APPLICATION n] IMPLICIT T` +/// +/// A helper object implementing [`FromBer`](crate::FromBer) and [`FromDer`](crate::FromDer), to +/// parse explicit application-tagged values. +/// +/// # Examples +/// +/// To parse a `[APPLICATION 0] IMPLICIT INTEGER` object: +/// +/// ```rust +/// use asn1_rs::{ApplicationImplicit, Error, FromBer, Integer, TaggedValue}; +/// +/// let bytes = &[0x60, 0x1, 0x2]; +/// +/// let (_, tagged) = ApplicationImplicit::<Integer, Error, 0>::from_ber(bytes).unwrap(); +/// assert_eq!(tagged, TaggedValue::implicit(Integer::from(2_u8))); +/// ``` +pub type ApplicationImplicit<T, E, const TAG: u32> = + TaggedValue<T, E, Implicit, { Class::APPLICATION }, TAG>; diff --git a/src/asn1_types/tagged/builder.rs b/src/asn1_types/tagged/builder.rs new file mode 100644 index 0000000..093b9d9 --- /dev/null +++ b/src/asn1_types/tagged/builder.rs @@ -0,0 +1,104 @@ +use super::{Error, Explicit, Implicit, TaggedParser}; +use crate::{Class, FromBer, FromDer, ParseResult, Tag}; +use core::marker::PhantomData; + +/// A builder for parsing tagged values (`IMPLICIT` or `EXPLICIT`) +/// +/// # Examples +/// +/// ``` +/// use asn1_rs::{Class, Tag, TaggedParserBuilder}; +/// +/// let parser = TaggedParserBuilder::explicit() +/// .with_class(Class::ContextSpecific) +/// .with_tag(Tag(0)) +/// .der_parser::<u32>(); +/// +/// let input = &[0xa0, 0x03, 0x02, 0x01, 0x02]; +/// let (rem, tagged) = parser(input).expect("parsing failed"); +/// +/// assert!(rem.is_empty()); +/// assert_eq!(tagged.tag(), Tag(0)); +/// assert_eq!(tagged.as_ref(), &2); +/// ``` +#[derive(Clone, Copy, Debug)] +pub struct TaggedParserBuilder<TagKind, E = Error> { + class: Class, + tag: Tag, + tag_kind: PhantomData<TagKind>, + _e: PhantomData<E>, +} + +impl<TagKind, E> TaggedParserBuilder<TagKind, E> { + /// Create a default `TaggedParserBuilder` builder + /// + /// `TagKind` must be specified as either [`Explicit`] or [`Implicit`] + /// + /// ``` + /// use asn1_rs::{Explicit, TaggedParserBuilder}; + /// + /// let builder = TaggedParserBuilder::<Explicit>::new(); + /// ``` + pub const fn new() -> Self { + TaggedParserBuilder { + class: Class::Universal, + tag: Tag(0), + tag_kind: PhantomData, + _e: PhantomData, + } + } + + /// Set the expected `Class` for the builder + pub const fn with_class(self, class: Class) -> Self { + Self { class, ..self } + } + + /// Set the expected `Tag` for the builder + pub const fn with_tag(self, tag: Tag) -> Self { + Self { tag, ..self } + } +} + +impl<E> TaggedParserBuilder<Explicit, E> { + /// Create a `TagParser` builder for `EXPLICIT` tagged values + pub const fn explicit() -> Self { + TaggedParserBuilder::new() + } +} + +impl<E> TaggedParserBuilder<Implicit, E> { + /// Create a `TagParser` builder for `IMPLICIT` tagged values + pub const fn implicit() -> Self { + TaggedParserBuilder::new() + } +} + +impl<TagKind, E> TaggedParserBuilder<TagKind, E> { + /// Create the BER parser from the builder parameters + /// + /// This method will consume the builder and return a parser (to be used as a function). + pub fn ber_parser<'a, T>( + self, + ) -> impl Fn(&'a [u8]) -> ParseResult<'a, TaggedParser<'a, TagKind, T, E>, E> + where + TaggedParser<'a, TagKind, T, E>: FromBer<'a, E>, + E: From<Error>, + { + move |bytes: &[u8]| TaggedParser::<TagKind, T, E>::parse_ber(self.class, self.tag, bytes) + } +} + +impl<TagKind, E> TaggedParserBuilder<TagKind, E> { + /// Create the DER parser from the builder parameters + /// + /// This method will consume the builder and return a parser (to be used as a function). + pub fn der_parser<'a, T>( + self, + ) -> impl Fn(&'a [u8]) -> ParseResult<'a, TaggedParser<'a, TagKind, T, E>, E> + where + TaggedParser<'a, TagKind, T, E>: FromDer<'a, E>, + E: From<Error>, + { + move |bytes: &[u8]| TaggedParser::<TagKind, T, E>::parse_der(self.class, self.tag, bytes) + } +} diff --git a/src/asn1_types/tagged/explicit.rs b/src/asn1_types/tagged/explicit.rs new file mode 100644 index 0000000..7fdbcce --- /dev/null +++ b/src/asn1_types/tagged/explicit.rs @@ -0,0 +1,262 @@ +use crate::*; +use core::convert::TryFrom; +use core::marker::PhantomData; + +impl<'a, T, E, const CLASS: u8, const TAG: u32> TryFrom<Any<'a>> + for TaggedValue<T, E, Explicit, CLASS, TAG> +where + T: FromBer<'a, E>, + E: From<Error>, +{ + type Error = E; + + fn try_from(any: Any<'a>) -> Result<Self, E> { + Self::try_from(&any) + } +} + +impl<'a, 'b, T, E, const CLASS: u8, const TAG: u32> TryFrom<&'b Any<'a>> + for TaggedValue<T, E, Explicit, CLASS, TAG> +where + T: FromBer<'a, E>, + E: From<Error>, +{ + type Error = E; + + fn try_from(any: &'b Any<'a>) -> Result<Self, E> { + any.tag().assert_eq(Tag(TAG))?; + any.header.assert_constructed()?; + if any.class() as u8 != CLASS { + let class = Class::try_from(CLASS).ok(); + return Err(Error::unexpected_class(class, any.class()).into()); + } + let (_, inner) = match T::from_ber(any.data) { + Ok((rem, res)) => (rem, res), + Err(Err::Error(e)) | Err(Err::Failure(e)) => return Err(e), + Err(Err::Incomplete(n)) => return Err(Error::Incomplete(n).into()), + }; + Ok(TaggedValue::explicit(inner)) + } +} + +impl<'a, T, E, const CLASS: u8, const TAG: u32> FromDer<'a, E> + for TaggedValue<T, E, Explicit, CLASS, TAG> +where + T: FromDer<'a, E>, + E: From<Error>, +{ + fn from_der(bytes: &'a [u8]) -> ParseResult<'a, Self, E> { + let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?; + any.tag() + .assert_eq(Tag(TAG)) + .map_err(|e| Err::Error(e.into()))?; + any.header + .assert_constructed() + .map_err(|e| Err::Error(e.into()))?; + if any.class() as u8 != CLASS { + let class = Class::try_from(CLASS).ok(); + return Err(Err::Error( + Error::unexpected_class(class, any.class()).into(), + )); + } + let (_, inner) = T::from_der(any.data)?; + Ok((rem, TaggedValue::explicit(inner))) + } +} + +impl<T, E, const CLASS: u8, const TAG: u32> CheckDerConstraints + for TaggedValue<T, E, Explicit, CLASS, TAG> +where + T: CheckDerConstraints, +{ + fn check_constraints(any: &Any) -> Result<()> { + any.header.length.assert_definite()?; + let (_, inner) = Any::from_ber(any.data)?; + T::check_constraints(&inner)?; + Ok(()) + } +} + +#[cfg(feature = "std")] +impl<T, E, const CLASS: u8, const TAG: u32> ToDer for TaggedValue<T, E, Explicit, CLASS, TAG> +where + T: ToDer, +{ + fn to_der_len(&self) -> Result<usize> { + let sz = self.inner.to_der_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 inner_len = self.inner.to_der_len()?; + let class = + Class::try_from(CLASS).map_err(|_| SerializeError::InvalidClass { class: CLASS })?; + let header = Header::new(class, true, self.tag(), Length::Definite(inner_len)); + header.write_der_header(writer).map_err(Into::into) + } + + fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + self.inner.write_der(writer) + } +} + +/// A helper object to parse `[ n ] EXPLICIT T` +/// +/// A helper object implementing [`FromBer`] and [`FromDer`], to parse tagged +/// optional values. +/// +/// This helper expects context-specific tags. +/// See [`TaggedValue`] or [`TaggedParser`] for more generic implementations if needed. +/// +/// # Examples +/// +/// To parse a `[0] EXPLICIT INTEGER` object: +/// +/// ```rust +/// use asn1_rs::{Error, FromBer, Integer, TaggedExplicit, TaggedValue}; +/// +/// let bytes = &[0xa0, 0x03, 0x2, 0x1, 0x2]; +/// +/// // If tagged object is present (and has expected tag), parsing succeeds: +/// let (_, tagged) = TaggedExplicit::<Integer, Error, 0>::from_ber(bytes).unwrap(); +/// assert_eq!(tagged, TaggedValue::explicit(Integer::from(2))); +/// ``` +pub type TaggedExplicit<T, E, const TAG: u32> = TaggedValue<T, E, Explicit, CONTEXT_SPECIFIC, TAG>; + +// implementations for TaggedParser + +impl<'a, T, E> TaggedParser<'a, Explicit, T, E> { + pub const fn new_explicit(class: Class, tag: u32, inner: T) -> Self { + Self { + header: Header::new(class, true, Tag(tag), Length::Definite(0)), + inner, + tag_kind: PhantomData, + _e: PhantomData, + } + } + + /// Parse a BER tagged value and apply the provided parsing function to content + /// + /// After parsing, the sequence object and header are discarded. + /// + /// Note: this function is provided for `Explicit`, but there is not difference between + /// explicit or implicit tags. The `op` function is responsible of handling the content. + #[inline] + pub fn from_ber_and_then<F>( + class: Class, + tag: u32, + bytes: &'a [u8], + op: F, + ) -> ParseResult<'a, T, E> + where + F: FnOnce(&'a [u8]) -> ParseResult<T, E>, + E: From<Error>, + { + Any::from_ber_and_then(class, tag, bytes, op) + } + + /// Parse a DER tagged value and apply the provided parsing function to content + /// + /// After parsing, the sequence object and header are discarded. + /// + /// Note: this function is provided for `Explicit`, but there is not difference between + /// explicit or implicit tags. The `op` function is responsible of handling the content. + #[inline] + pub fn from_der_and_then<F>( + class: Class, + tag: u32, + bytes: &'a [u8], + op: F, + ) -> ParseResult<'a, T, E> + where + F: FnOnce(&'a [u8]) -> ParseResult<T, E>, + E: From<Error>, + { + Any::from_der_and_then(class, tag, bytes, op) + } +} + +impl<'a, T, E> FromBer<'a, E> for TaggedParser<'a, Explicit, T, E> +where + T: FromBer<'a, E>, + E: From<Error>, +{ + fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, E> { + let (rem, any) = Any::from_ber(bytes).map_err(Err::convert)?; + let header = any.header; + let (_, inner) = T::from_ber(any.data)?; + let tagged = TaggedParser { + header, + inner, + tag_kind: PhantomData, + _e: PhantomData, + }; + Ok((rem, tagged)) + } +} + +impl<'a, T, E> FromDer<'a, E> for TaggedParser<'a, Explicit, T, E> +where + T: FromDer<'a, E>, + E: From<Error>, +{ + fn from_der(bytes: &'a [u8]) -> ParseResult<'a, Self, E> { + let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?; + let header = any.header; + let (_, inner) = T::from_der(any.data)?; + let tagged = TaggedParser { + header, + inner, + tag_kind: PhantomData, + _e: PhantomData, + }; + Ok((rem, tagged)) + } +} + +impl<'a, T> CheckDerConstraints for TaggedParser<'a, Explicit, T> +where + T: CheckDerConstraints, +{ + fn check_constraints(any: &Any) -> Result<()> { + any.header.length.assert_definite()?; + let (_, inner_any) = Any::from_der(any.data)?; + T::check_constraints(&inner_any)?; + Ok(()) + } +} + +#[cfg(feature = "std")] +impl<'a, T> ToDer for TaggedParser<'a, Explicit, T> +where + T: ToDer, +{ + fn to_der_len(&self) -> Result<usize> { + let sz = self.inner.to_der_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 inner_len = self.inner.to_der_len()?; + let header = Header::new(self.class(), true, self.tag(), Length::Definite(inner_len)); + header.write_der_header(writer).map_err(Into::into) + } + + fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + self.inner.write_der(writer) + } +} diff --git a/src/asn1_types/tagged/helpers.rs b/src/asn1_types/tagged/helpers.rs new file mode 100644 index 0000000..dfb1018 --- /dev/null +++ b/src/asn1_types/tagged/helpers.rs @@ -0,0 +1,103 @@ +use super::{Explicit, Implicit, TaggedParser}; +use crate::{Any, Error, FromDer, Header, ParseResult, Tag, Tagged}; +use nom::error::ParseError; +use nom::{Err, IResult}; + +// helper functions for parsing tagged objects + +pub fn parse_der_tagged_explicit<'a, IntoTag, T, E>( + tag: IntoTag, +) -> impl FnMut(&'a [u8]) -> ParseResult<TaggedParser<'a, Explicit, T, E>, E> +where + IntoTag: Into<Tag>, + TaggedParser<'a, Explicit, T, E>: FromDer<'a, E>, + E: From<Error>, +{ + let tag = tag.into(); + move |i| { + let (rem, tagged) = TaggedParser::from_der(i)?; + tagged.assert_tag(tag).map_err(|e| Err::Error(e.into()))?; + Ok((rem, tagged)) + } +} + +pub fn parse_der_tagged_explicit_g<'a, IntoTag, T, F, E>( + tag: IntoTag, + f: F, +) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], T, E> +where + F: Fn(&'a [u8], Header<'a>) -> IResult<&'a [u8], T, E>, + E: ParseError<&'a [u8]> + From<Error>, + IntoTag: Into<Tag>, +{ + let tag = tag.into(); + parse_der_container(tag, move |any: Any<'a>| { + any.header + .assert_tag(tag) + .map_err(|e| nom::Err::convert(e.into()))?; + f(any.data, any.header) + }) +} + +pub fn parse_der_tagged_implicit<'a, IntoTag, T, E>( + tag: IntoTag, +) -> impl FnMut(&'a [u8]) -> ParseResult<TaggedParser<'a, Implicit, T, E>, E> +where + IntoTag: Into<Tag>, + // T: TryFrom<Any<'a>, Error = Error> + Tagged, + TaggedParser<'a, Implicit, T, E>: FromDer<'a, E>, + E: From<Error>, +{ + let tag = tag.into(); + move |i| { + let (rem, tagged) = TaggedParser::from_der(i)?; + tagged + .assert_tag(tag) + .map_err(|e| nom::Err::convert(e.into()))?; + Ok((rem, tagged)) + } +} + +pub fn parse_der_tagged_implicit_g<'a, IntoTag, T, F, E>( + tag: IntoTag, + f: F, +) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], T, E> +where + F: Fn(&'a [u8], Tag, Header<'a>) -> IResult<&'a [u8], T, E>, + E: ParseError<&'a [u8]> + From<Error>, + IntoTag: Into<Tag>, + T: Tagged, +{ + let tag = tag.into(); + parse_der_container(tag, move |any: Any<'a>| { + // verify tag of external header + any.header + .assert_tag(tag) + .map_err(|e| nom::Err::convert(e.into()))?; + // build a fake header with the expected tag + let Any { header, data } = any; + let header = Header { + tag: T::TAG, + ..header.clone() + }; + f(data, tag, header) + }) +} + +fn parse_der_container<'a, T, F, E>( + tag: Tag, + f: F, +) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], T, E> +where + F: Fn(Any<'a>) -> IResult<&'a [u8], T, E>, + E: ParseError<&'a [u8]> + From<Error>, +{ + move |i: &[u8]| { + let (rem, any) = Any::from_der(i).map_err(nom::Err::convert)?; + any.header + .assert_tag(tag) + .map_err(|e| nom::Err::convert(e.into()))?; + let (_, output) = f(any)?; + Ok((rem, output)) + } +} diff --git a/src/asn1_types/tagged/implicit.rs b/src/asn1_types/tagged/implicit.rs new file mode 100644 index 0000000..53a921c --- /dev/null +++ b/src/asn1_types/tagged/implicit.rs @@ -0,0 +1,287 @@ +use crate::*; +use core::convert::TryFrom; +use core::marker::PhantomData; + +impl<'a, T, E, const CLASS: u8, const TAG: u32> TryFrom<Any<'a>> + for TaggedValue<T, E, Implicit, CLASS, TAG> +where + T: TryFrom<Any<'a>, Error = E>, + T: Tagged, + E: From<Error>, +{ + type Error = E; + + fn try_from(any: Any<'a>) -> Result<Self, E> { + TryFrom::try_from(&any) + } +} + +impl<'a, 'b, E, T, const CLASS: u8, const TAG: u32> TryFrom<&'b Any<'a>> + for TaggedValue<T, E, Implicit, CLASS, TAG> +where + T: TryFrom<Any<'a>, Error = E>, + T: Tagged, + E: From<Error>, +{ + type Error = E; + + fn try_from(any: &'b Any<'a>) -> Result<Self, E> { + any.tag().assert_eq(Tag(TAG))?; + // XXX if input is empty, this function is not called + + if any.class() as u8 != CLASS { + let class = Class::try_from(CLASS).ok(); + return Err(Error::unexpected_class(class, any.class()).into()); + } + let any = Any { + header: Header { + tag: T::TAG, + ..any.header.clone() + }, + data: any.data, + }; + match T::try_from(any) { + Ok(inner) => Ok(TaggedValue::implicit(inner)), + Err(e) => Err(e), + } + } +} + +impl<'a, T, E, const CLASS: u8, const TAG: u32> FromDer<'a, E> + for TaggedValue<T, E, Implicit, CLASS, TAG> +where + T: TryFrom<Any<'a>, Error = E>, + T: Tagged, + E: From<Error>, +{ + fn from_der(bytes: &'a [u8]) -> ParseResult<'a, Self, E> { + let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?; + any.tag() + .assert_eq(Tag(TAG)) + .map_err(|e| Err::Error(e.into()))?; + if any.class() as u8 != CLASS { + let class = Class::try_from(CLASS).ok(); + return Err(Err::Error( + Error::unexpected_class(class, any.class()).into(), + )); + } + let any = Any { + header: Header { + tag: T::TAG, + ..any.header.clone() + }, + data: any.data, + }; + match T::try_from(any) { + Ok(inner) => Ok((rem, TaggedValue::implicit(inner))), + Err(e) => Err(nom::Err::Error(e)), + } + } +} + +impl<T, E, const CLASS: u8, const TAG: u32> CheckDerConstraints + for TaggedValue<T, E, Implicit, CLASS, TAG> +where + T: CheckDerConstraints, + T: Tagged, +{ + fn check_constraints(any: &Any) -> Result<()> { + any.header.length.assert_definite()?; + let header = any.header.clone().with_tag(T::TAG); + let inner = Any::new(header, any.data); + T::check_constraints(&inner)?; + Ok(()) + } +} + +#[cfg(feature = "std")] +impl<T, E, const CLASS: u8, const TAG: u32> ToDer for TaggedValue<T, E, Implicit, CLASS, TAG> +where + T: ToDer, +{ + fn to_der_len(&self) -> Result<usize> { + self.inner.to_der_len() + } + + fn write_der(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let class = + Class::try_from(CLASS).map_err(|_| SerializeError::InvalidClass { class: CLASS })?; + let mut v = Vec::new(); + let inner_len = self.inner.write_der_content(&mut v)?; + // XXX X.690 section 8.14.3: if implicing tagging was used [...]: + // XXX a) the encoding shall be constructed if the base encoding is constructed, and shall be primitive otherwise + let constructed = matches!(TAG, 16 | 17); + let header = Header::new(class, constructed, self.tag(), Length::Definite(inner_len)); + let sz = header.write_der_header(writer)?; + let sz = sz + writer.write(&v)?; + Ok(sz) + } + + fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let mut sink = std::io::sink(); + let class = + Class::try_from(CLASS).map_err(|_| SerializeError::InvalidClass { class: CLASS })?; + let inner_len = self.inner.write_der_content(&mut sink)?; + // XXX X.690 section 8.14.3: if implicing tagging was used [...]: + // XXX a) the encoding shall be constructed if the base encoding is constructed, and shall be primitive otherwise + let constructed = matches!(TAG, 16 | 17); + let header = Header::new(class, constructed, self.tag(), Length::Definite(inner_len)); + header.write_der_header(writer).map_err(Into::into) + } + + fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + self.inner.write_der(writer) + } +} + +/// A helper object to parse `[ n ] IMPLICIT T` +/// +/// A helper object implementing [`FromBer`] and [`FromDer`], to parse tagged +/// optional values. +/// +/// This helper expects context-specific tags. +/// See [`TaggedValue`] or [`TaggedParser`] for more generic implementations if needed. +/// +/// # Examples +/// +/// To parse a `[0] IMPLICIT INTEGER OPTIONAL` object: +/// +/// ```rust +/// use asn1_rs::{Error, FromBer, Integer, TaggedImplicit, TaggedValue}; +/// +/// let bytes = &[0xa0, 0x1, 0x2]; +/// +/// let (_, tagged) = TaggedImplicit::<Integer, Error, 0>::from_ber(bytes).unwrap(); +/// assert_eq!(tagged, TaggedValue::implicit(Integer::from(2))); +/// ``` +pub type TaggedImplicit<T, E, const TAG: u32> = TaggedValue<T, E, Implicit, CONTEXT_SPECIFIC, TAG>; + +impl<'a, T, E> FromBer<'a, E> for TaggedParser<'a, Implicit, T, E> +where + T: TryFrom<Any<'a>, Error = E>, + T: Tagged, + E: From<Error>, +{ + fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, E> { + let (rem, any) = Any::from_ber(bytes).map_err(Err::convert)?; + let Any { header, data } = any; + let any = Any { + header: Header { + tag: T::TAG, + ..header.clone() + }, + data, + }; + match T::try_from(any) { + Ok(t) => { + let tagged_value = TaggedParser { + header, + inner: t, + tag_kind: PhantomData, + _e: PhantomData, + }; + Ok((rem, tagged_value)) + } + Err(e) => Err(nom::Err::Error(e)), + } + } +} + +// implementations for TaggedParser + +impl<'a, T, E> TaggedParser<'a, Implicit, T, E> { + pub const fn new_implicit(class: Class, constructed: bool, tag: u32, inner: T) -> Self { + Self { + header: Header::new(class, constructed, Tag(tag), Length::Definite(0)), + inner, + tag_kind: PhantomData, + _e: PhantomData, + } + } +} + +impl<'a, T, E> FromDer<'a, E> for TaggedParser<'a, Implicit, T, E> +where + T: TryFrom<Any<'a>, Error = E>, + T: CheckDerConstraints, + T: Tagged, + E: From<Error>, +{ + fn from_der(bytes: &'a [u8]) -> ParseResult<'a, Self, E> { + let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?; + let Any { header, data } = any; + let any = Any { + header: Header { + tag: T::TAG, + ..header.clone() + }, + data, + }; + T::check_constraints(&any).map_err(|e| nom::Err::Error(e.into()))?; + match T::try_from(any) { + Ok(t) => { + let tagged_value = TaggedParser { + header, + inner: t, + tag_kind: PhantomData, + _e: PhantomData, + }; + Ok((rem, tagged_value)) + } + Err(e) => Err(nom::Err::Error(e)), + } + } +} + +impl<'a, T> CheckDerConstraints for TaggedParser<'a, Implicit, T> +where + T: CheckDerConstraints, + T: Tagged, +{ + fn check_constraints(any: &Any) -> Result<()> { + any.header.length.assert_definite()?; + let any = Any { + header: Header { + tag: T::TAG, + ..any.header.clone() + }, + data: any.data, + }; + T::check_constraints(&any)?; + Ok(()) + } +} + +#[cfg(feature = "std")] +impl<'a, T> ToDer for TaggedParser<'a, Implicit, T> +where + T: ToDer, +{ + fn to_der_len(&self) -> Result<usize> { + self.inner.to_der_len() + } + + fn write_der(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let mut v = Vec::new(); + let inner_len = self.inner.write_der_content(&mut v)?; + // XXX X.690 section 8.14.3: if implicing tagging was used [...]: + // XXX a) the encoding shall be constructed if the base encoding is constructed, and shall be primitive otherwise + let header = Header::new(self.class(), false, self.tag(), Length::Definite(inner_len)); + let sz = header.write_der_header(writer)?; + let sz = sz + writer.write(&v)?; + Ok(sz) + } + + fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let mut sink = std::io::sink(); + let inner_len = self.inner.write_der_content(&mut sink)?; + // XXX X.690 section 8.14.3: if implicing tagging was used [...]: + // XXX a) the encoding shall be constructed if the base encoding is constructed, and shall be primitive otherwise + let header = Header::new(self.class(), false, self.tag(), Length::Definite(inner_len)); + header.write_der_header(writer).map_err(Into::into) + } + + fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + self.inner.write_der_content(writer) + } +} diff --git a/src/asn1_types/tagged/optional.rs b/src/asn1_types/tagged/optional.rs new file mode 100644 index 0000000..00504df --- /dev/null +++ b/src/asn1_types/tagged/optional.rs @@ -0,0 +1,240 @@ +use super::{explicit::TaggedExplicit, implicit::TaggedImplicit}; +use crate::*; + +/// Helper object to parse TAGGED OPTIONAL types (explicit or implicit) +/// +/// This object can be used similarly to a builder pattern, to specify the expected class and +/// tag of the object to parse, and the content parsing function. +/// +/// The content parsing function takes two arguments: the outer header, and the data. +/// +/// It can be used for both EXPLICIT or IMPLICIT tagged objects by using parsing functions that +/// expect a header (or not) in the contents. +/// +/// The [`OptTaggedParser::from`] method is a shortcut to build an object with `ContextSpecific` +/// class and the given tag. The [`OptTaggedParser::new`] method is more generic. +/// +/// See also [`OptTaggedExplicit`] and [`OptTaggedImplicit`] for alternatives that implement [`FromBer`]/ +/// [`FromDer`]. +/// +/// # Examples +/// +/// To parse a `[APPLICATION 0] EXPLICIT INTEGER OPTIONAL` object: +/// +/// ```rust +/// use asn1_rs::{Class, FromDer, Integer, Tag, OptTaggedParser}; +/// +/// let bytes = &[0x60, 0x03, 0x2, 0x1, 0x2]; +/// +/// let (_, tagged) = OptTaggedParser::new(Class::Application, Tag(0)) +/// .parse_der(bytes, |_, data| Integer::from_der(data)) +/// .unwrap(); +/// +/// assert_eq!(tagged, Some(Integer::from(2))); +/// ``` +/// +/// To parse a `[0] IMPLICIT INTEGER OPTIONAL` object: +/// +/// ```rust +/// use asn1_rs::{Error, Integer, OptTaggedParser}; +/// +/// let bytes = &[0xa0, 0x1, 0x2]; +/// +/// let (_, tagged) = OptTaggedParser::from(0) +/// .parse_der::<_, Error, _>(bytes, |_, data| Ok((&[], Integer::new(data)))) +/// .unwrap(); +/// +/// assert_eq!(tagged, Some(Integer::from(2))); +/// ``` +#[derive(Debug)] +pub struct OptTaggedParser { + /// The expected class for the object to parse + pub class: Class, + /// The expected tag for the object to parse + pub tag: Tag, +} + +impl OptTaggedParser { + /// Build a new `OptTaggedParser` object. + /// + /// If using `Class::ContextSpecific`, using [`OptTaggedParser::from`] with either a `Tag` or `u32` is + /// a shorter way to build this object. + pub const fn new(class: Class, tag: Tag) -> Self { + OptTaggedParser { class, tag } + } + + pub const fn universal(tag: u32) -> Self { + Self::new(Class::Universal, Tag(tag)) + } + + pub const fn tagged(tag: u32) -> Self { + Self::new(Class::ContextSpecific, Tag(tag)) + } + + pub const fn application(tag: u32) -> Self { + Self::new(Class::Application, Tag(tag)) + } + + pub const fn private(tag: u32) -> Self { + Self::new(Class::Private, Tag(tag)) + } + + /// Parse input as BER, and apply the provided function to parse object. + /// + /// Returns the remaining bytes, and `Some(T)` if expected tag was found, else `None`. + /// + /// This function returns an error if tag was found but has a different class, or if parsing fails. + /// + /// # Examples + /// + /// To parse a `[0] EXPLICIT INTEGER OPTIONAL` object: + /// + /// ```rust + /// use asn1_rs::{FromBer, Integer, OptTaggedParser}; + /// + /// let bytes = &[0xa0, 0x03, 0x2, 0x1, 0x2]; + /// + /// let (_, tagged) = OptTaggedParser::from(0) + /// .parse_ber(bytes, |_, data| Integer::from_ber(data)) + /// .unwrap(); + /// + /// assert_eq!(tagged, Some(Integer::from(2))); + /// ``` + pub fn parse_ber<'a, T, E, F>(&self, bytes: &'a [u8], f: F) -> ParseResult<'a, Option<T>, E> + where + F: Fn(Header, &'a [u8]) -> ParseResult<'a, T, E>, + E: From<Error>, + { + if bytes.is_empty() { + return Ok((bytes, None)); + } + let (rem, any) = Any::from_ber(bytes).map_err(Err::convert)?; + if any.tag() != self.tag { + return Ok((bytes, None)); + } + if any.class() != self.class { + return Err(Err::Error( + Error::unexpected_class(Some(self.class), any.class()).into(), + )); + } + let Any { header, data } = any; + let (_, res) = f(header, data)?; + Ok((rem, Some(res))) + } + + /// Parse input as DER, and apply the provided function to parse object. + /// + /// Returns the remaining bytes, and `Some(T)` if expected tag was found, else `None`. + /// + /// This function returns an error if tag was found but has a different class, or if parsing fails. + /// + /// # Examples + /// + /// To parse a `[0] EXPLICIT INTEGER OPTIONAL` object: + /// + /// ```rust + /// use asn1_rs::{FromDer, Integer, OptTaggedParser}; + /// + /// let bytes = &[0xa0, 0x03, 0x2, 0x1, 0x2]; + /// + /// let (_, tagged) = OptTaggedParser::from(0) + /// .parse_der(bytes, |_, data| Integer::from_der(data)) + /// .unwrap(); + /// + /// assert_eq!(tagged, Some(Integer::from(2))); + /// ``` + pub fn parse_der<'a, T, E, F>(&self, bytes: &'a [u8], f: F) -> ParseResult<'a, Option<T>, E> + where + F: Fn(Header, &'a [u8]) -> ParseResult<'a, T, E>, + E: From<Error>, + { + if bytes.is_empty() { + return Ok((bytes, None)); + } + let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?; + if any.tag() != self.tag { + return Ok((bytes, None)); + } + if any.class() != self.class { + return Err(Err::Error( + Error::unexpected_class(Some(self.class), any.class()).into(), + )); + } + let Any { header, data } = any; + let (_, res) = f(header, data)?; + Ok((rem, Some(res))) + } +} + +impl From<Tag> for OptTaggedParser { + /// Build a `TaggedOptional` object with class `ContextSpecific` and given tag + #[inline] + fn from(tag: Tag) -> Self { + OptTaggedParser::new(Class::ContextSpecific, tag) + } +} + +impl From<u32> for OptTaggedParser { + /// Build a `TaggedOptional` object with class `ContextSpecific` and given tag + #[inline] + fn from(tag: u32) -> Self { + OptTaggedParser::new(Class::ContextSpecific, Tag(tag)) + } +} + +/// A helper object to parse `[ n ] EXPLICIT T OPTIONAL` +/// +/// A helper object implementing [`FromBer`] and [`FromDer`], to parse tagged +/// optional values. +/// +/// This helper expects context-specific tags. +/// Use `Option<` [`TaggedValue`] `>` for a more generic implementation. +/// +/// # Examples +/// +/// To parse a `[0] EXPLICIT INTEGER OPTIONAL` object: +/// +/// ```rust +/// use asn1_rs::{Error, FromBer, Integer, OptTaggedExplicit, TaggedValue}; +/// +/// let bytes = &[0xa0, 0x03, 0x2, 0x1, 0x2]; +/// +/// // If tagged object is present (and has expected tag), parsing succeeds: +/// let (_, tagged) = OptTaggedExplicit::<Integer, Error, 0>::from_ber(bytes).unwrap(); +/// assert_eq!(tagged, Some(TaggedValue::explicit(Integer::from(2)))); +/// +/// // If tagged object is not present or has different tag, parsing +/// // also succeeds (returning None): +/// let (_, tagged) = OptTaggedExplicit::<Integer, Error, 0>::from_ber(&[]).unwrap(); +/// assert_eq!(tagged, None); +/// let (_, tagged) = OptTaggedExplicit::<Integer, Error, 1>::from_ber(bytes).unwrap(); +/// assert_eq!(tagged, None); +/// ``` +pub type OptTaggedExplicit<T, E, const TAG: u32> = Option<TaggedExplicit<T, E, TAG>>; + +/// A helper object to parse `[ n ] IMPLICIT T OPTIONAL` +/// +/// A helper object implementing [`FromBer`] and [`FromDer`], to parse tagged +/// optional values. +/// +/// This helper expects context-specific tags. +/// Use `Option<` [`TaggedValue`] `>` for a more generic implementation. +/// +/// # Examples +/// +/// To parse a `[0] IMPLICIT INTEGER OPTIONAL` object: +/// +/// ```rust +/// use asn1_rs::{Error, FromBer, Integer, OptTaggedImplicit, TaggedValue}; +/// +/// let bytes = &[0xa0, 0x1, 0x2]; +/// +/// let (_, tagged) = OptTaggedImplicit::<Integer, Error, 0>::from_ber(bytes).unwrap(); +/// assert_eq!(tagged, Some(TaggedValue::implicit(Integer::from(2)))); +/// +/// // If tagged object is not present or has different tag, parsing +/// // also succeeds (returning None): +/// let (_, tagged) = OptTaggedImplicit::<Integer, Error, 0>::from_ber(&[]).unwrap(); +/// assert_eq!(tagged, None); +/// ``` +pub type OptTaggedImplicit<T, E, const TAG: u32> = Option<TaggedImplicit<T, E, TAG>>; diff --git a/src/asn1_types/tagged/parser.rs b/src/asn1_types/tagged/parser.rs new file mode 100644 index 0000000..243b081 --- /dev/null +++ b/src/asn1_types/tagged/parser.rs @@ -0,0 +1,78 @@ +use crate::*; +use core::marker::PhantomData; + +#[derive(Debug, PartialEq, Eq)] +pub struct TaggedParser<'a, TagKind, T, E = Error> { + pub header: Header<'a>, + pub inner: T, + + pub(crate) tag_kind: PhantomData<TagKind>, + pub(crate) _e: PhantomData<E>, +} + +impl<'a, TagKind, T, E> TaggedParser<'a, TagKind, T, E> { + pub const fn new(header: Header<'a>, inner: T) -> Self { + TaggedParser { + header, + inner, + tag_kind: PhantomData, + _e: PhantomData, + } + } + + pub const fn assert_class(&self, class: Class) -> Result<()> { + self.header.assert_class(class) + } + + pub const fn assert_tag(&self, tag: Tag) -> Result<()> { + self.header.assert_tag(tag) + } + + #[inline] + pub const fn class(&self) -> Class { + self.header.class + } + + #[inline] + pub const fn tag(&self) -> Tag { + self.header.tag + } +} + +impl<'a, TagKind, T, E> AsRef<T> for TaggedParser<'a, TagKind, T, E> { + fn as_ref(&self) -> &T { + &self.inner + } +} + +impl<'a, TagKind, T, E> TaggedParser<'a, TagKind, T, E> +where + Self: FromBer<'a, E>, + E: From<Error>, +{ + pub fn parse_ber(class: Class, tag: Tag, bytes: &'a [u8]) -> ParseResult<'a, Self, E> { + let (rem, t) = TaggedParser::<TagKind, T, E>::from_ber(bytes)?; + t.assert_class(class).map_err(|e| Err::Error(e.into()))?; + t.assert_tag(tag).map_err(|e| Err::Error(e.into()))?; + Ok((rem, t)) + } +} + +impl<'a, TagKind, T, E> TaggedParser<'a, TagKind, T, E> +where + Self: FromDer<'a, E>, + E: From<Error>, +{ + pub fn parse_der(class: Class, tag: Tag, bytes: &'a [u8]) -> ParseResult<'a, Self, E> { + let (rem, t) = TaggedParser::<TagKind, T, E>::from_der(bytes)?; + t.assert_class(class).map_err(|e| Err::Error(e.into()))?; + t.assert_tag(tag).map_err(|e| Err::Error(e.into()))?; + Ok((rem, t)) + } +} + +impl<'a, TagKind, T, E> DynTagged for TaggedParser<'a, TagKind, T, E> { + fn tag(&self) -> Tag { + self.tag() + } +} diff --git a/src/asn1_types/tagged/private.rs b/src/asn1_types/tagged/private.rs new file mode 100644 index 0000000..5d8708a --- /dev/null +++ b/src/asn1_types/tagged/private.rs @@ -0,0 +1,42 @@ +use crate::{Class, Explicit, Implicit, TaggedValue}; + +/// A helper object to parse `[PRIVATE n] EXPLICIT T` +/// +/// A helper object implementing [`FromBer`](crate::FromBer) and [`FromDer`](crate::FromDer), to +/// parse explicit private-tagged values. +/// +/// # Examples +/// +/// To parse a `[PRIVATE 0] EXPLICIT INTEGER` object: +/// +/// ```rust +/// use asn1_rs::{Error, FromBer, Integer, PrivateExplicit, TaggedValue}; +/// +/// let bytes = &[0xe0, 0x03, 0x2, 0x1, 0x2]; +/// +/// // If tagged object is present (and has expected tag), parsing succeeds: +/// let (_, tagged) = PrivateExplicit::<Integer, Error, 0>::from_ber(bytes).unwrap(); +/// assert_eq!(tagged, TaggedValue::explicit(Integer::from(2))); +/// ``` +pub type PrivateExplicit<T, E, const TAG: u32> = + TaggedValue<T, E, Explicit, { Class::PRIVATE }, TAG>; + +/// A helper object to parse `[PRIVATE n] IMPLICIT T` +/// +/// A helper object implementing [`FromBer`](crate::FromBer) and [`FromDer`](crate::FromDer), to +/// parse implicit private-tagged values. +/// +/// # Examples +/// +/// To parse a `[PRIVATE 0] IMPLICIT INTEGER` object: +/// +/// ```rust +/// use asn1_rs::{Error, FromBer, Integer, PrivateImplicit, TaggedValue}; +/// +/// let bytes = &[0xe0, 0x1, 0x2]; +/// +/// let (_, tagged) = PrivateImplicit::<Integer, Error, 0>::from_ber(bytes).unwrap(); +/// assert_eq!(tagged, TaggedValue::implicit(Integer::from(2_u8))); +/// ``` +pub type PrivateImplicit<T, E, const TAG: u32> = + TaggedValue<T, E, Implicit, { Class::PRIVATE }, TAG>; diff --git a/src/asn1_types/utctime.rs b/src/asn1_types/utctime.rs new file mode 100644 index 0000000..8604914 --- /dev/null +++ b/src/asn1_types/utctime.rs @@ -0,0 +1,222 @@ +use crate::datetime::decode_decimal; +use crate::*; +use core::convert::TryFrom; +use core::fmt; +#[cfg(feature = "datetime")] +use time::OffsetDateTime; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct UtcTime(pub ASN1DateTime); + +impl UtcTime { + pub const fn new(datetime: ASN1DateTime) -> Self { + UtcTime(datetime) + } + + pub fn from_bytes(bytes: &[u8]) -> Result<Self> { + // X.680 section 43 defines a UniversalTime as a VisibleString restricted to: + // + // a) the six digits YYMMDD where YY is the two low-order digits of the Christian year, MM is the month + // (counting January as 01), and DD is the day of the month (01 to 31); and + // b) either: + // 1) the four digits hhmm where hh is hour (00 to 23) and mm is minutes (00 to 59); or + // 2) the six digits hhmmss where hh and mm are as in 1) above, and ss is seconds (00 to 59); and + // c) either: + // 1) the character Z ; or + // 2) one of the characters + or - , followed by hhmm, where hh is hour and mm is minutes. + // + // XXX // RFC 5280 requires mandatory seconds and Z-normalized time zone + let (year, month, day, hour, minute, rem) = match bytes { + [year1, year2, mon1, mon2, day1, day2, hour1, hour2, min1, min2, rem @ ..] => { + let year = decode_decimal(Self::TAG, *year1, *year2)?; + let month = decode_decimal(Self::TAG, *mon1, *mon2)?; + let day = decode_decimal(Self::TAG, *day1, *day2)?; + let hour = decode_decimal(Self::TAG, *hour1, *hour2)?; + let minute = decode_decimal(Self::TAG, *min1, *min2)?; + (year, month, day, hour, minute, rem) + } + _ => return Err(Self::TAG.invalid_value("malformed time string (not yymmddhhmm)")), + }; + if rem.is_empty() { + return Err(Self::TAG.invalid_value("malformed time string")); + } + // check for seconds + let (second, rem) = match rem { + [sec1, sec2, rem @ ..] => { + let second = decode_decimal(Self::TAG, *sec1, *sec2)?; + (second, rem) + } + _ => (0, rem), + }; + if month > 12 || day > 31 || hour > 23 || minute > 59 || second > 59 { + return Err(Self::TAG.invalid_value("time components with invalid values")); + } + if rem.is_empty() { + return Err(Self::TAG.invalid_value("malformed time string")); + } + let tz = match rem { + [b'Z'] => ASN1TimeZone::Z, + [b'+', h1, h2, m1, m2] => { + let hh = decode_decimal(Self::TAG, *h1, *h2)?; + let mm = decode_decimal(Self::TAG, *m1, *m2)?; + ASN1TimeZone::Offset(hh as i8, mm as i8) + } + [b'-', h1, h2, m1, m2] => { + let hh = decode_decimal(Self::TAG, *h1, *h2)?; + let mm = decode_decimal(Self::TAG, *m1, *m2)?; + ASN1TimeZone::Offset(-(hh as i8), mm as i8) + } + _ => return Err(Self::TAG.invalid_value("malformed time string: no time zone")), + }; + Ok(UtcTime(ASN1DateTime::new( + year as u32, + month, + day, + hour, + minute, + second, + None, + tz, + ))) + // match *bytes { + // [year1, year2, mon1, mon2, day1, day2, hour1, hour2, min1, min2, sec1, sec2, b'Z'] => { + // let year = decode_decimal(Self::TAG, year1, year2)?; + // let month = decode_decimal(Self::TAG, mon1, mon2)?; + // let day = decode_decimal(Self::TAG, day1, day2)?; + // let hour = decode_decimal(Self::TAG, hour1, hour2)?; + // let minute = decode_decimal(Self::TAG, min1, min2)?; + // let second = decode_decimal(Self::TAG, sec1, sec2)?; + + // // RFC 5280 rules for interpreting the year + // let year = if year >= 50 { year + 1900 } else { year + 2000 }; + + // Ok(UtcTime::new(year, month, day, hour, minute, second)) + // } + // _ => Err(Error::InvalidValue), + // } + } + + /// Return a ISO 8601 combined date and time with time zone. + #[cfg(feature = "datetime")] + #[cfg_attr(docsrs, doc(cfg(feature = "datetime")))] + #[inline] + pub fn utc_datetime(&self) -> Result<OffsetDateTime> { + self.0.to_datetime() + } + + /// Return an adjusted ISO 8601 combined date and time with time zone. + /// According to Universal time definition in X.680 we add 2000 years + /// from 0 to 49 year and 1900 otherwise. + #[cfg(feature = "datetime")] + #[cfg_attr(docsrs, doc(cfg(feature = "datetime")))] + #[inline] + pub fn utc_adjusted_datetime(&self) -> Result<OffsetDateTime> { + self.0.to_datetime().and_then(|dt| { + let year = dt.year(); + // We follow the Universal time definition in X.680 for interpreting + // the adjusted year + let year = if year >= 50 { year + 1900 } else { year + 2000 }; + time::Date::from_calendar_date(year, dt.month(), dt.day()) + .map(|d| dt.replace_date(d)) + .map_err(|_e| Self::TAG.invalid_value("Invalid adjusted date")) + }) + } + + /// Returns the number of non-leap seconds since the midnight on January 1, 1970. + #[cfg(feature = "datetime")] + #[cfg_attr(docsrs, doc(cfg(feature = "datetime")))] + pub fn timestamp(&self) -> Result<i64> { + let dt = self.0.to_datetime()?; + Ok(dt.unix_timestamp()) + } +} + +impl<'a> TryFrom<Any<'a>> for UtcTime { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<UtcTime> { + TryFrom::try_from(&any) + } +} + +impl<'a, 'b> TryFrom<&'b Any<'a>> for UtcTime { + type Error = Error; + + fn try_from(any: &'b Any<'a>) -> Result<UtcTime> { + any.tag().assert_eq(Self::TAG)?; + #[allow(clippy::trivially_copy_pass_by_ref)] + fn is_visible(b: &u8) -> bool { + 0x20 <= *b && *b <= 0x7f + } + if !any.data.iter().all(is_visible) { + return Err(Error::StringInvalidCharset); + } + + UtcTime::from_bytes(any.data) + } +} + +impl fmt::Display for UtcTime { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let dt = &self.0; + match dt.tz { + ASN1TimeZone::Z | ASN1TimeZone::Undefined => write!( + f, + "{:04}-{:02}-{:02} {:02}:{:02}:{:02}Z", + dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second + ), + ASN1TimeZone::Offset(hh, mm) => { + let (s, hh) = if hh > 0 { ('+', hh) } else { ('-', -hh) }; + write!( + f, + "{:04}-{:02}-{:02} {:02}:{:02}:{:02}{}{:02}{:02}", + dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, s, hh, mm + ) + } + } + } +} + +impl CheckDerConstraints for UtcTime { + fn check_constraints(_any: &Any) -> Result<()> { + Ok(()) + } +} + +impl DerAutoDerive for UtcTime {} + +impl Tagged for UtcTime { + const TAG: Tag = Tag::UtcTime; +} + +#[cfg(feature = "std")] +impl ToDer for UtcTime { + fn to_der_len(&self) -> Result<usize> { + // data: + // - 6 bytes for YYMMDD + // - 6 for hhmmss in DER (X.690 section 11.8.2) + // - 1 for the character Z in DER (X.690 section 11.8.1) + // data length: 13 + // + // thus, length will always be on 1 byte (short length) and + // class+structure+tag also on 1 + // + // total: 15 = 1 (class+constructed+tag) + 1 (length) + 13 + Ok(15) + } + + fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + // see above for length value + writer.write(&[Self::TAG.0 as u8, 13]).map_err(Into::into) + } + + fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + write!( + writer, + "{:02}{:02}{:02}{:02}{:02}{:02}Z", + self.0.year, self.0.month, self.0.day, self.0.hour, self.0.minute, self.0.second, + )?; + // write_fmt returns (), see above for length value + Ok(13) + } +} |